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

RustBeginner
Практиковаться сейчас

Введение

Многие полезные программы взаимодействуют с файловой системой: читают существующие данные, обрабатывают их и сохраняют результат. Такие операции могут завершиться ошибкой, поэтому файловые API Rust возвращают значения Result, а не предполагают гарантированный успех.

Вы изучите подготовленный журнал службы, представите расположение файлов с помощью Path, прочитаете текст средствами std::fs, создадите небольшой отчёт, запишете его в другой файл и передадите ошибки ввода-вывода с помощью ?.

Чтение текстового файла через Path

На этом этапе вы изучите подготовленный журнал и прочитаете его в строку Rust String.

Проект находится в /home/labex/project/log-summary. В нём есть входной файл data/events.log, пустой каталог output для последующего отчёта и исходный файл программы src/main.rs. Перейдите в каталог проекта:

cd /home/labex/project/log-summary

Откройте входной файл в nano, чтобы увидеть, какие данные прочитает программа:

nano data/events.log

В файле находятся три короткие записи. Не изменяйте файл: нажмите Ctrl+X, чтобы выйти. Затем откройте исходный код Rust:

nano src/main.rs

Две подготовленные строки use делают функции работы с файлами из std::fs и тип Path доступными под короткими именами. Это только импорт необходимых компонентов, а не основная тема данной лабораторной работы; в следующем модуле подробно объясняется работа с путями и объявлениями use. Тип возвращаемого значения std::io::Result<()> означает, что функция main либо завершается с пустым значением успешного результата (), либо возвращает ошибку ввода-вывода. Последнее выражение Ok(()) обозначает успешное завершение.

Замените комментарий Step 1 следующим кодом:

    let input_path = Path::new("data/events.log");
    let contents = fs::read_to_string(input_path)?;
    println!("Input: {}", input_path.display());
    println!("Contents:\n{}", contents.trim_end());

Path::new создаёт заимствованное представление пути в файловой системе, не открывая файл. Относительный путь вычисляется относительно каталога проекта, из которого вы запускаете Cargo. fs::read_to_string открывает файл, читает текст в кодировке UTF-8 в принадлежащую программе строку String и возвращает Result. Оператор ? извлекает String при успешном выполнении или возвращает ошибку ввода-вывода из main. Метод display() форматирует путь для вывода пользователю, а trim_end() не позволяет завершающей новой строке файла создать дополнительную пустую строку в выводе.

Сохраните файл клавишами Ctrl+O, нажмите Enter, затем выйдите клавишами Ctrl+X. Проверьте и запустите программу:

cargo check
cargo run --quiet
Input: data/events.log
Contents:
INFO service started
WARN disk nearing limit
INFO backup complete

Отображаемый путь и все три записи подтверждают, что программа прочитала нужный файл.

Подсчёт записей в загруженном тексте

На этом этапе вы подсчитаете логические строки в уже загруженной в память строке String.

Метод lines() создаёт итератор по строкам текста, не изменяя исходную строку. Метод count() потребляет итератор и возвращает количество полученных элементов. Откройте исходный файл:

nano src/main.rs

Замените комментарий Step 2 следующим кодом:

    let line_count = contents.lines().count();
    println!("Line count: {line_count}");

Сохраните файл и выйдите, затем запустите программу:

cargo run --quiet

Новая последняя строка должна выглядеть так:

Line count: 3

Код работает независимо от того, заканчивается ли входной файл символом новой строки, поскольку lines() определяет логические записи, а не подсчитывает байты символов новой строки.

Формирование устойчивой строки отчёта

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

Строковый метод starts_with проверяет префикс. Если объединить его с уже знакомыми адаптерами итераторов, программа сможет подсчитать только строки, начинающиеся с WARN. Откройте исходный файл:

nano src/main.rs

Замените комментарий Step 3 следующим кодом:

    let warning_count = contents
        .lines()
        .filter(|line| line.starts_with("WARN"))
        .count();
    let summary = format!("lines: {line_count}\nwarnings: {warning_count}\n");
    println!("Summary ready:\n{}", summary.trim_end());

Предикат фильтра оставляет только строки с предупреждениями, а count() потребляет отфильтрованный итератор. Макрос format! возвращает новую строку String, а не выводит её на экран; явные символы \n создают две строки и добавляют завершающую новую строку, подходящую для текстового файла.

Сохраните файл и выйдите, затем проверьте и запустите программу:

cargo check
cargo run --quiet

Последняя часть вывода должна выглядеть так:

Summary ready:
lines: 3
warnings: 1

Теперь отчёт находится в памяти и готов к записи в файловую систему.

Запись и повторное чтение файла отчёта

На этом этапе вы запишете отчёт в подготовленный каталог вывода и прочитаете его обратно, чтобы проверить результат.

fs::write(path, data) создаёт файл или полностью заменяет его прежнее содержимое переданными байтами; эта функция не добавляет данные в конец файла. Как и чтение, запись может завершиться ошибкой и возвращает Result ввода-вывода, поэтому вы передадите ошибку с помощью ?. Откройте исходный файл:

nano src/main.rs

Замените комментарий Step 4 следующим кодом:

    let output_path = Path::new("output/summary.txt");
    fs::write(output_path, &summary)?;
    let saved = fs::read_to_string(output_path)?;
    println!("Saved {}:\n{}", output_path.display(), saved.trim_end());

&summary заимствует отчёт на время чтения его байтов функцией write, поэтому строка String по-прежнему принадлежит программе. Повторное чтение файла проверяет фактически сохранённый результат, а не предполагает, что запись обязательно прошла успешно.

Сохраните файл и выйдите, затем проверьте и запустите программу:

cargo check
cargo run --quiet

Последняя часть вывода должна выглядеть так:

Saved output/summary.txt:
lines: 3
warnings: 1

Для окончательной проверки откройте созданный файл:

nano output/summary.txt

Вы должны увидеть те же две строки отчёта. Нажмите Ctrl+X, чтобы выйти без изменений.

Итоги

Вы представили расположение файлов с помощью Path, прочитали текст в кодировке UTF-8 через fs::read_to_string, передали ошибки ввода-вывода с помощью ?, сформировали отчёт по загруженным записям, записали результат через fs::write и повторно прочитали сохранённый файл, чтобы проверить его содержимое.