Как правильно читать и записывать файлы в Rust 1.x? (де-факто способ)
Правильный подход: буферизированное чтение и запись с обработкой ошибок
В Rust 1.x де-факто стандартом для работы с файлами является использование BufReader и BufWriter из std::io. Это даёт эффективную буферизацию и позволяет обрабатывать файлы любого размера без загрузки в память целиком. Ниже — идиоматичное решение вашей задачи.
use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::path::Path;
fn process_file(input_path: &Path, output_path: &Path) -> io::Result<()> {
// Открываем входной файл с буферизированным чтением
let input_file = File::open(input_path)?;
let reader = BufReader::new(input_file);
// Создаём выходной файл с буферизированной записью
let output_file = File::create(output_path)?;
let mut writer = BufWriter::new(output_file);
// Обрабатываем строки по одной
for line in reader.lines() {
let line = line?; // Обрабатываем возможную ошибку чтения
let processed = format!("PREFIX: {}\n", line);
writer.write_all(processed.as_bytes())?;
}
// Явно сбрасываем буфер (необязательно, но хорошая практика)
writer.flush()?;
Ok(())
}
fn main() {
let input = Path::new("input.txt");
let output = Path::new("output.txt");
match process_file(input, output) {
Ok(()) => println!("Файл успешно обработан"),
Err(e) => eprintln!("Ошибка: {}", e),
}
}
Ключевые моменты идиоматичного подхода
- Буферизация:
BufReaderиBufWriterавтоматически управляют буферами (по умолчанию 8 КБ), что значительно ускоряет I/O операции. - Обработка ошибок: Используется
io::Result<()>и оператор?для проброса ошибок наверх. Это чище, чемunwrap(). - Построчное чтение:
reader.lines()возвращает итератор строк, не загружая весь файл в память. Каждая строка обрабатывается и записывается сразу. - Пути: Используется
std::path::Pathдля типобезопасной работы с путями. - Явный flush: Вызов
flush()гарантирует, что все данные записаны на диск. При падении программы до этого момента можно потерять данные.
Альтернативы и дополнения
Если вам нужно просто скопировать файл целиком, используйте std::fs::copy. Для работы с бинарными данными — read_exact и write_all. Для чтения файла целиком (если он небольшой) — std::fs::read_to_string.
Важно: BufReader::lines() удаляет символ конца строки (\n или \r\n), поэтому в примере мы добавляем \n вручную. Это идиоматично и контролируемо.
Данный подход считается де-факто стандартом в Rust 1.x, используется в официальной документации и production-коде крупных проектов (например, ripgrep, cargo).
<<Асинхронный подход с tokio (для высоконагруженных приложений)
Если ваш проект использует асинхронный рантайм (например, tokio), то для файловых операций лучше применять асинхронные версии. Это особенно актуально для веб-серверов или приложений, обрабатывающих множество файлов одновременно.
use tokio::fs::File;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
use std::path::Path;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = Path::new("input.txt");
let output = Path::new("output.txt");
let input_file = File::open(input).await?;
let reader = BufReader::new(input_file);
let mut lines = reader.lines();
let output_file = File::create(output).await?;
let mut writer = BufWriter::new(output_file);
while let Some(line) = lines.next_line().await? {
let processed = format!("PREFIX: {}\n", line);
writer.write_all(processed.as_bytes()).await?;
}
writer.flush().await?;
println!("Готово!");
Ok(())
}
Этот код практически идентичен синхронному, но использует async/await. Преимущества: не блокирует поток выполнения, что критично для серверов. Недостаток: требуется зависимость tokio (или async-std).
Для простых скриптов и CLI-утилит синхронный подход из первого ответа предпочтительнее — он проще и не требует дополнительных крейтов.
Ответов пока нет.
Войдите чтобы оставить ответ