Работа с файловой системой в Rust: Полное руководство
Rust — это современный системный язык программирования, который предлагает мощные и безопасные инструменты для работы с файловой системой. Благодаря строгой системе типов и модели владения, Rust позволяет избежать многих ошибок, связанных с управлением файлами, таких как гонки данных или утечки ресурсов. В этой статье мы подробно разберем, как выполнять основные операции с файловой системой в Rust: создание, чтение, запись, копирование и удаление файлов и директорий. Все примеры кода написаны с использованием стандартной библиотеки std::fs.
Подготовка проекта и импорт модулей
Для начала создайте новый проект с помощью Cargo:
cargo new file_system_demo
cd file_system_demo
Откройте файл src/main.rs и добавьте следующие импорты:
use std::fs;
use std::io::{self, Write, Read};
use std::path::Path;
Модуль std::fs предоставляет функции для работы с файлами и директориями. std::io содержит трейты для ввода/вывода, а std::path::Path — для работы с путями.
Создание и запись файлов
Самый простой способ создать файл и записать в него данные — использовать функцию fs::write. Она принимает путь к файлу и содержимое в виде байтов или строки.
fn main() -> io::Result<()> {
// Создаем файл и записываем в него строку
fs::write("hello.txt", "Привет, Rust!")?;
println!("Файл создан успешно.");
Ok(())
}
Если вам нужно больше контроля, используйте fs::File и методы write или write_all:
fn main() -> io::Result<()> {
let mut file = fs::File::create("data.txt")?;
file.write_all(b"Строка данных\n")?;
file.write_all("Еще одна строка\n".as_bytes())?;
Ok(())
}
Обратите внимание на ? — это оператор распространения ошибок. Если операция завершится неудачей, ошибка будет возвращена из функции.
Чтение файлов
Для чтения всего содержимого файла в строку используйте fs::read_to_string:
fn main() -> io::Result<()> {
let contents = fs::read_to_string("hello.txt")?;
println!("Содержимое файла:\n{}", contents);
Ok(())
}
Если файл содержит бинарные данные, используйте fs::read, которая возвращает Vec<u8>:
fn main() -> io::Result<()> {
let data = fs::read("image.png")?;
println!("Прочитано {} байт", data.len());
Ok(())
}
Для построчного чтения удобно использовать BufReader:
fn main() -> io::Result<()> {
let file = fs::File::open("data.txt")?;
let reader = io::BufReader::new(file);
for line in reader.lines() {
let line = line?;
println!("{}", line);
}
Ok(())
}
Работа с директориями
Создание новой директории выполняется с помощью fs::create_dir или fs::create_dir_all (для вложенных путей):
fn main() -> io::Result<()> {
// Создаем одну директорию
fs::create_dir("my_folder")?;
// Создаем вложенные директории (аналог mkdir -p)
fs::create_dir_all("a/b/c")?;
println!("Директории созданы.");
Ok(())
}
Чтобы проверить существование файла или директории, используйте Path::exists:
fn main() {
let path = Path::new("hello.txt");
if path.exists() {
println!("Файл существует.");
} else {
println!("Файл не найден.");
}
}
Получить список содержимого директории можно с помощью fs::read_dir:
fn main() -> io::Result<()> {
let entries = fs::read_dir(".")?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
println!("[DIR] {}", path.display());
} else {
println!("[FILE] {}", path.display());
}
}
Ok(())
}
Копирование, перемещение и удаление
Для копирования файлов используйте fs::copy:
fn main() -> io::Result<()> {
fs::copy("hello.txt", "hello_backup.txt")?;
println!("Файл скопирован.");
Ok(())
}
Переименование или перемещение файла выполняется с помощью fs::rename:
fn main() -> io::Result<()> {
fs::rename("hello.txt", "greeting.txt")?;
println!("Файл переименован.");
Ok(())
}
Удаление файла — fs::remove_file, а директории — fs::remove_dir (только пустая) или fs::remove_dir_all (рекурсивно):
fn main() -> io::Result<()> {
// Удалить файл
fs::remove_file("greeting.txt")?;
// Удалить пустую директорию
fs::remove_dir("my_folder")?;
// Удалить директорию со всем содержимым
fs::remove_dir_all("a")?;
println!("Очистка завершена.");
Ok(())
}
Обработка ошибок и best practices
Всегда обрабатывайте ошибки. В примерах выше мы использовали io::Result, но в реальных приложениях стоит добавлять более информативные сообщения:
fn main() {
match fs::read_to_string("config.toml") {
Ok(contents) => println!("Конфиг: {}", contents),
Err(e) => eprintln!("Ошибка чтения файла: {}", e),
}
}
Также рекомендуется использовать ?mut для открытия файла в режиме чтения/записи:
fn main() -> io::Result<()> {
let mut file = fs::OpenOptions::new()
.read(true)
.write(true)