Работа с файловой системой в Rust: на практике
Rust — современный системный язык, который даёт разработчику полный контроль над памятью и при этом гарантирует безопасность. Работа с файловой системой — одна из базовых задач, которую приходится решать практически в любом проекте. В этой статье мы разберём, как читать и записывать файлы, управлять директориями и обходить каталоги на практических примерах. Все примеры написаны на Rust и готовы к использованию.
Основные модули и структуры
В Rust за работу с файловой системой отвечает модуль std::fs (file system) и std::path для работы с путями. Ключевые типы:
std::fs::File— представляет открытый файл.std::fs::read_dir— итератор по содержимому директории.std::path::Pathиstd::path::PathBuf— для работы с путями.
Подключение модулей:
use std::fs;
use std::path::Path;
Чтение файла целиком
Самый простой способ прочитать небольшой текстовый файл — использовать функцию fs::read_to_string. Она возвращает Result<String>.
use std::fs;
fn main() {
let content = fs::read_to_string("example.txt")
.expect("Не удалось прочитать файл");
println!("Содержимое файла:\n{}", content);
}
Если файла не существует или нет прав доступа, программа завершится с паникой. В реальных проектах используйте match или оператор ? для обработки ошибок.
Обработка ошибок при чтении
use std::fs;
fn read_file(path: &str) -> Result<String, std::io::Error> {
fs::read_to_string(path)
}
fn main() {
match read_file("config.txt") {
Ok(data) => println!("{}", data),
Err(e) => eprintln!("Ошибка: {}", e),
}
}
Запись данных в файл
Для записи используется fs::write (перезаписывает файл) или создание File с опциями OpenOptions.
Простая запись строки
use std::fs;
fn main() {
let data = "Hello, Rust!";
fs::write("output.txt", data)
.expect("Не удалось записать файл");
println!("Файл записан успешно");
}
Дозапись в конец файла
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("Не удалось записать");
}
Обратите внимание: OpenOptions позволяет гибко настроить режим — создание, перезапись, дозапись, чтение.
Работа с директориями
Создание, удаление и обход папок — частые задачи. Рассмотрим их.
Создание новой директории
use std::fs;
fn main() {
fs::create_dir("new_folder")
.expect("Не удалось создать папку");
// Создание вложенных папок (рекурсивно)
fs::create_dir_all("parent/child/grandchild")
.expect("Не удалось создать вложенные папки");
}
Удаление директории
use std::fs;
fn main() {
// Удаление только пустой папки
fs::remove_dir("empty_folder").ok();
// Рекурсивное удаление со всем содержимым
fs::remove_dir_all("temp_data")
.expect("Не удалось удалить папку");
}
Будьте осторожны с remove_dir_all — он безвозвратно удаляет всё содержимое.
Обход содержимого каталога
Итерация по файлам и папкам внутри директории:
use std::fs;
fn main() {
let entries = fs::read_dir(".")
.expect("Не удалось прочитать директорию");
for entry in entries {
let entry = entry.expect("Ошибка чтения записи");
let path = entry.path();
if path.is_dir() {
println!("[DIR] {}", path.display());
} else {
println!("[FILE] {}", path.display());
}
}
}
Если нужен рекурсивный обход (включая подпапки), используйте популярный крейт walkdir, но для простых задач достаточно вложенного цикла.
Рекурсивный обход без внешних зависимостей
use std::fs;
use std::path::Path;
fn visit_dirs(dir: &Path) -> std::io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
visit_dirs(&path)?;
} else {
println!("{}", path.display());
}
}
}
Ok(())
}
fn main() {
visit_dirs(Path::new("."))
.expect("Ошибка при обходе");
}
Функция visit_dirs рекурсивно заходит во все поддиректории и выводит пути к файлам. Обработка ошибок через ? делает код чистым и безопасным.
Проверка существования и метаданные
Часто нужно проверить, существует ли файл или папка, и получить информацию о них.
use std::fs;
use std::path::Path;
fn main() {
let path = Path::new("test.txt");
// Проверка существования
if path.exists() {
println!("Файл существует");
}
// Получение метаданных
if let Ok(metadata) = fs::metadata(path) {
println!("Размер: {} байт", metadata.len());
println!("Это директория: {}", metadata.is_dir());
println!("Это файл: {}", metadata.is_file());
// Время последнего изменения (в секундах с эпохи)
if let Ok(modified) = metadata.modified() {
println!("Изменён: {:?}", modified);
}
}
}
fs::metadata возвращает структуру Metadata с информацией о размере, правах доступа, типе и времени.