Работа с файловой системой в Rust: на практике
Rust — это современный системный язык программирования, который сочетает в себе высокую производительность и безопасность памяти. Одной из ключевых задач, с которой сталкивается каждый разработчик, является работа с файловой системой. В этой статье мы на практических примерах разберём, как читать и записывать файлы, создавать директории, обрабатывать ошибки и выполнять другие операции с файловой системой в Rust.
Основные модули для работы с файловой системой
В Rust вся работа с файловой системой сосредоточена в стандартной библиотеке. Основные модули:
std::fs— функции для работы с файлами и директориями (чтение, запись, создание, удаление).std::path— работа с путями (Path,PathBuf).std::io— ввод-вывод, включая трейтыReadиWrite.
Для начала работы достаточно импортировать нужные модули:
use std::fs;
use std::path::Path;
use std::io::prelude::*;
use std::fs::File;
Чтение файлов в Rust
Самый простой способ прочитать весь файл целиком — использовать функцию fs::read_to_string. Она возвращает Result<String>, что позволяет удобно обрабатывать ошибки.
use std::fs;
fn main() {
let file_path = "example.txt";
match fs::read_to_string(file_path) {
Ok(content) => println!("Содержимое файла:\n{}", content),
Err(e) => eprintln!("Ошибка чтения файла: {}", e),
}
}
Если нужно читать файл построчно (например, для больших файлов), используйте BufReader:
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
let file = File::open("example.txt").expect("Не удалось открыть файл");
let reader = BufReader::new(file);
for line in reader.lines() {
match line {
Ok(content) => println!("{}", content),
Err(e) => eprintln!("Ошибка чтения строки: {}", e),
}
}
}
Запись данных в файлы
Для записи данных в файл используйте fs::write (простая запись строки) или File::create с последующей записью через write! или writeln!.
use std::fs;
fn main() {
let content = "Привет, мир!\nЭто строка для записи.";
fs::write("output.txt", content).expect("Ошибка записи файла");
println!("Файл успешно записан");
}
Для последовательной записи или дополнения данных используйте OpenOptions:
use std::fs::OpenOptions;
use std::io::Write;
fn main() {
let mut file = OpenOptions::new()
.append(true) // дополнение файла
.create(true) // создать, если не существует
.open("log.txt")
.expect("Не удалось открыть файл");
writeln!(file, "Запись в лог: {}", chrono::Local::now()).expect("Ошибка записи");
}
Работа с директориями и путями
Rust предоставляет удобные средства для создания, удаления и обхода директорий. Рассмотрим основные операции.
Создание и удаление директорий
use std::fs;
fn main() {
// Создание одной директории
fs::create_dir("new_folder").expect("Ошибка создания директории");
// Создание вложенных директорий (аналог mkdir -p)
fs::create_dir_all("parent/child/grandchild").expect("Ошибка создания вложенных директорий");
// Удаление пустой директории
fs::remove_dir("new_folder").expect("Ошибка удаления директории");
// Удаление директории со всем содержимым
fs::remove_dir_all("parent").expect("Ошибка удаления директории с содержимым");
}
Обход содержимого директории
Для получения списка файлов и папок используйте fs::read_dir:
use std::fs;
fn main() {
let path = ".";
match fs::read_dir(path) {
Ok(entries) => {
for entry in entries {
match entry {
Ok(entry) => {
let file_name = entry.file_name();
let file_type = entry.file_type().unwrap_or_default();
let is_dir = if file_type.is_dir() { "📁" } else { "📄" };
println!("{} {}", is_dir, file_name.to_string_lossy());
}
Err(e) => eprintln!("Ошибка доступа к элементу: {}", e),
}
}
}
Err(e) => eprintln!("Ошибка чтения директории: {}", e),
}
}
Работа с путями через модуль Path
Модуль std::path позволяет безопасно манипулировать путями:
use std::path::Path;
fn main() {
let path = Path::new("/home/user/documents/file.txt");
println!("Родительская директория: {:?}", path.parent());
println!("Имя файла: {:?}", path.file_name());
println!("Расширение: {:?}", path.extension());
println!("Без расширения: {:?}", path.file_stem());
// Проверка существования
if path.exists() {
println!("Файл существует");
}
// Объединение путей
let new_path = path.with_file_name("new_file.rs");
println!("Новый путь: {:?}", new_path);
}
Обработка ошибок при работе с файловой системой
Все операции с файловой системой возвращают Result. Правильная обработка ошибок — залог надёжного кода. Рассмотрим два основных подхода.
Использование match для детальной обработки
use std::fs;
fn main() {
let result = fs::read_to_string("config.txt");
match result {
Ok(content) => println!("Конфиг: {}", content),
Err(e) => match e.kind() {
std::io::ErrorKind::NotFound => eprintln!("Файл не найден"),
std::io::ErrorKind::PermissionDenied => eprintln!("Нет прав доступа"),
_ => eprintln!("Неизвестная ошибка: {}", e),
},
}
}
Проброс ошибок с помощью оператора ?
use std::fs;
use std::io;
fn read_user_config() -> io::Result<String> {
let content = fs::read_to_string("user_config.txt")?;
Ok(content)
}
fn main() {
match read_user_config() {
Ok(content) => println!("