Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Файловая система

Note

Работа с файловой системой редко нужна при написании бекенд приложений, поэтому мы касаемся этой темы очень поверхностно.

Для работы с файловой системой стандартная библиотека Rust предоставляет такие модули:

  • std::fs — содержит функциональность для работы непосредственно с объектами файловой системы: файлами, директориями, ссылками.
  • std::io — содержит функциональность для работы с операциями ввода/вывода

Чтение и запись файла

Для примера работы с файлом напишем простую программу, которая:

  • открывает новый файл, записывает в него текст, закрывает файл
  • открывает этот же файл, добавляет в него строку, закрывает файл
  • опять открывает файл и считывает из него всё содержимое в строку
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};

fn main() -> io::Result<()> {
    {
        // Создаёт новый файл (или перезаписывает имеющийся)
        let mut file = File::create("file.txt")?;
        // Записывает в файл байты
        file.write_all("First line\n".as_bytes())?;
        file.flush()?; // Очистка буфера вывода
        file.write_all("Second line\n".as_bytes())?;
    }

    {
        // Открываем файл для добавления
        let mut file = OpenOptions::new()
            .append(true)
            .create(false)
            .open("file.txt")?;
        file.write_all("Third line\n".as_bytes())?;
    }

    {
        // Открываем файл для чтения
        let mut file = File::open("file.txt")?;
        let mut buffer = String::new();
        file.read_to_string(&mut buffer)?;
        println!("{buffer}");
    }

    Ok(())
}

Первое, что бросается в глаза: мы открываем файл для записи, но не закрываем его. Дело в том, что для типа File реализован трэйт Drop: при выходе объекта из скоупа деструктор сбросит буфер вывода (вызовом flush), а после закроет файл.

Тип io::Result<T>, в который завёрнут результат всех методов ввода/вывода — это псевдоним, который объявлен как:

#![allow(unused)]
fn main() {
pub type Result<T> = result::Result<T, std::io::Error>;
}

то есть это обычный Result, который параметризирован стандартной ошибкой для I/O операций.

Чтение директории

Напишем программу, которая выводит имена файлов и директорий, находящихся в текущем каталоге:

use std::{ffi::OsString, fs::FileType};

fn main() -> std::io::Result<()> {
    for read_entry in std::fs::read_dir(".")? {
        if let Ok(entry) = read_entry {
            let entry_name: OsString = entry.file_name();
            let file_type: FileType = entry.file_type()?;
            println!("{entry_name:?} {file_type:?}");
        }
    }
    Ok(())
}

Вывод программы:

"target" FileType { is_file: false, is_dir: true, is_symlink: false, .. }
"Cargo.lock" FileType { is_file: true, is_dir: false, is_symlink: false, .. }
"src" FileType { is_file: false, is_dir: true, is_symlink: false, .. }
"Cargo.toml" FileType { is_file: true, is_dir: false, is_symlink: false, .. }

Как видите, имя директории хранится в объекте типа OsString. Это важный момент, который следует разобрать подробнее.

OsString

Почти все функции для работы с файловой системой в качестве строк используют не String, а OsString. Этот тип хранит строки в том представлении, в котором строки хранятся в текущей операционной системе.

Отдельный тип строки используется, потому что:

  • В Unix-подобных системах имена файлов хранятся в виде последовательности ненулевых байт, которая содержит строку в UTF-8 или другой кодировке.
  • На Windows имена файлов представлены последовательностями ненулевых двухбайтных значений (UTF-16).
  • Rust строки всегда хранятся в UTF-8 кодировке, причем нулевые символы допустимы.

Для OsString реализованы From<String> и From<&str>, которые позволяют легко преобразовать “обычную” строку в OsString.

use std::ffi::OsString;

fn main() {
    let string = "text";
    let os_string = OsString::from(string);
}

Как для типа String есть парный тип — строковый-слайс &str, так и для OsString есть соответствующий тип — &OsStr.

use std::ffi::{OsStr, OsString};

fn main() {
    let string = "text";
    let os_string = OsString::from(string);
    let os_str: &OsStr = &os_string;
}

В большинстве функций для работы с путями и именами файлов используется тип Path. Фактически, этот тип является просто обёрткой над OsStr:

#![allow(unused)]
fn main() {
pub struct Path {
    inner: OsStr,
}
}

Стандартная библиотека предоставляет From/Into и AsRef преобразования, которые позволяют бесшовно конвертировать Rust строки в Path.

Например, сигнатура метода File::create, который мы использовали в примере создания файла, имеет такой вид:

#![allow(unused)]
fn main() {
pub fn create<P: AsRef<Path>>(path: P) -> io::Result<File>
}

И именно потому что для &str определён AsRef<Path>, мы смогли вызвать этот метод как:

#![allow(unused)]
fn main() {
let mut file = File::create("file.txt")?;
}