Работа с файловой системой в Rust: практическое руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Работа с файловой системой в 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!("                

Блоги

Рекомендаций по книгам