Введение в работу с файловой системой в Rust
Rust — это современный системный язык программирования, который предлагает мощные и безопасные средства для взаимодействия с файловой системой. В отличие от многих других языков, Rust гарантирует безопасность работы с памятью и потоками, что особенно важно при операциях ввода-вывода. В этой статье мы подробно разберём, как создавать, читать, записывать файлы и директории, управлять путями и обрабатывать ошибки с помощью стандартной библиотеки std::fs и std::path.
Материал будет полезен как новичкам, которые только знакомятся с Rust, так и опытным разработчикам, желающим освежить знания. Все примеры кода готовы к запуску и сопровождаются комментариями.
Основные модули и импорты
Для работы с файловой системой в Rust используются два ключевых модуля:
std::fs— предоставляет функции для операций с файлами и папками (чтение, запись, удаление, копирование и т.д.).std::path— содержит типыPathиPathBufдля работы с путями.
Для большинства задач достаточно добавить следующие импорты:
use std::fs;
use std::path::Path;
use std::io::prelude::*; // для работы с потоками
Работа с файлами: чтение и запись
Чтение содержимого файла
Самый простой способ прочитать весь файл в строку — использовать функцию fs::read_to_string. Она возвращает Result<String>, поэтому нужно обработать возможную ошибку.
use std::fs;
fn read_file(path: &str) -> Result<String, std::io::Error> {
let content = fs::read_to_string(path)?;
Ok(content)
}
fn main() {
match read_file("example.txt") {
Ok(text) => println!("Содержимое файла:\n{}", text),
Err(e) => eprintln!("Ошибка чтения: {}", e),
}
}
Если нужно прочитать файл как бинарные данные, используйте fs::read, который возвращает Vec<u8>.
Запись в файл
Для записи строки в файл применяется fs::write. Она создаст файл, если его нет, или перезапишет существующий.
use std::fs;
fn write_file(path: &str, content: &str) -> Result<(), std::io::Error> {
fs::write(path, content)?;
println!("Файл успешно записан");
Ok(())
}
fn main() {
let data = "Hello, Rust!\nВторая строка.";
if let Err(e) = write_file("output.txt", data) {
eprintln!("Ошибка записи: {}", e);
}
}
Для дозаписи в конец файла или более тонкого контроля используйте OpenOptions:
use std::fs::OpenOptions;
use std::io::Write;
fn append_to_file(path: &str, line: &str) -> Result<(), std::io::Error> {
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open(path)?;
writeln!(file, "{}", line)?;
Ok(())
}
Работа с директориями
Создание и удаление папок
Для создания директории используйте fs::create_dir (одна папка) или fs::create_dir_all (рекурсивно, как mkdir -p).
use std::fs;
fn main() -> Result<(), std::io::Error> {
// Создание одной папки
fs::create_dir("my_folder")?;
// Рекурсивное создание вложенных папок
fs::create_dir_all("data/subdir/logs")?;
// Удаление пустой папки
fs::remove_dir("my_folder")?;
// Рекурсивное удаление папки со всем содержимым
fs::remove_dir_all("data")?;
Ok(())
}
Чтение содержимого директории
Функция fs::read_dir возвращает итератор по записям директории. Каждая запись — это DirEntry, из которой можно получить имя, тип и метаданные.
use std::fs;
fn list_directory(path: &str) -> Result<(), std::io::Error> {
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
let file_name = entry.file_name();
let file_type = entry.file_type()?;
if file_type.is_dir() {
println!("[DIR] {}", file_name.to_string_lossy());
} else if file_type.is_file() {
println!("[FILE] {}", file_name.to_string_lossy());
}
}
Ok(())
}
fn main() {
if let Err(e) = list_directory(".") {
eprintln!("Ошибка: {}", e);
}
}
Работа с путями: Path и PathBuf
В Rust пути представлены типами Path (неизменяемый, заимствованный) и PathBuf (собственный, изменяемый). Они кроссплатформенны и корректно обрабатывают разделители.
use std::path::Path;
fn main() {
let path = Path::new("/home/user/docs/example.txt");
// Получение компонентов пути
println!("Родитель: {:?}", path.parent());
println!("Имя файла: {:?}", path.file_name());
println!("Расширение: {:?}", path.extension());
println!("Без расширения: {:?}", path.file_stem());
// Проверка существования
println!("Существует: {}", path.exists());
println!("Это файл: {}", path.is_file());
println!("Это папка: {}", path.is_dir());
// Сборка пути
let mut new_path = path.to_path_buf();
new_path.set_extension("md");
println!("Новый путь: {}", new_path.display());
}
Для объединения путей используйте метод join:
let base = Path::new("/var/log");
let full = base.join("app.log");
// Результат: /var/log/app.log
Проверка существования и метаданные
Перед выполнением операций часто нужно проверить, существует ли файл или папка, а также получить метаданные (размер, время изменения, права доступа).
use std::fs;
use std::path::Path;
fn file_info(path: &str) ->