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

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

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

Начать курс

Работа с файловой системой в 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 с информацией о размере, правах доступа, типе и времени.

Практический пример:

Блоги

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