Какой де-факто способ чтения и записи файлов в Rust 1.x?

Когда Rust был сравнительно новым, я видел слишком много способов чтения и записи файлов. Многие из них чрезвычайно беспорядочные фрагменты, которые кто-то придумал для своего блога, а 99% примеров, которые я нашел (даже в Stack Overflow), - это нестабильные сборки, которые больше не работают. Теперь, когда Rust стабилен, что такое простой, читаемый, не панирующий фрагмент для чтения или записи файлов?

Это ближе всего к тому, что работает с точки зрения чтения текстового файла, но оно все еще не компилируется, хотя я уверен, что включил все, что у меня было. Это основано на фрагменте, который я нашел в Google+ из всех мест, и единственное, что я изменил, это то, что старый BufferedReader теперь просто BufReader:

use std::fs::File;
use std::io::BufReader;
use std::path::Path;

fn main() {
    let path = Path::new("./textfile");
    let mut file = BufReader::new(File::open(&path));
    for line in file.lines() {
        println!("{}", line);
    }
}

Компилятор жалуется:

error: the trait bound `std::result::Result<std::fs::File, std::io::Error>: std::io::Read` is not satisfied [--explain E0277]
 --> src/main.rs:7:20
  |>
7 |>     let mut file = BufReader::new(File::open(&path));
  |>                    ^^^^^^^^^^^^^^
note: required by `std::io::BufReader::new`

error: no method named `lines` found for type `std::io::BufReader<std::result::Result<std::fs::File, std::io::Error>>` in the current scope
 --> src/main.rs:8:22
  |>
8 |>     for line in file.lines() {
  |>                      ^^^^^

Подводя итог, я ищу:

  • Краткость
  • читабельность
  • охватывает все возможные ошибки.
  • не паникует

Ответ 1

Ни одна из функций, которые я здесь показываю, сама по себе не паникует, но я использую expect, потому что я не знаю, какая обработка ошибок лучше всего подойдет для вашего приложения. Прочитайте главу "Язык программирования Rust" об обработке ошибок, чтобы понять, как правильно обрабатывать ошибки в вашей собственной программе.

Ржавчина 1.26 и выше

Если вы не хотите заботиться о базовых деталях, есть однострочные функции для чтения и записи.

Прочитать файл в String

use std::fs;

fn main() {
    let data = fs::read_to_string("/etc/hosts").expect("Unable to read file");
    println!("{}", data);
}

Читать файл как Vec<u8>

use std::fs;

fn main() {
    let data = fs::read("/etc/hosts").expect("Unable to read file");
    println!("{}", data.len());
}

Написать файл

use std::fs;

fn main() {
    let data = "Some data!";
    fs::write("/tmp/foo", data).expect("Unable to write file");
}

Rust 1.0 и выше

Эти формы немного более многословны, чем однострочные функции, которые выделяют для вас String или Vec, но они более мощные, так как вы можете повторно использовать выделенные данные или добавлять к существующему объекту.

Чтение данных

Чтение файла требует двух основных частей: File и Read.

Прочитать файл в String

use std::fs::File;
use std::io::Read;

fn main() {
    let mut data = String::new();
    let mut f = File::open("/etc/hosts").expect("Unable to open file");
    f.read_to_string(&mut data).expect("Unable to read string");
    println!("{}", data);
}

Читать файл как Vec<u8>

use std::fs::File;
use std::io::Read;

fn main() {
    let mut data = Vec::new();
    let mut f = File::open("/etc/hosts").expect("Unable to open file");
    f.read_to_end(&mut data).expect("Unable to read data");
    println!("{}", data.len());
}

Написать файл

Запись файла аналогична, за исключением того, что мы используем черту Write и мы всегда записываем байты. Вы можете преобразовать String/&str в байты с помощью as_bytes:

use std::fs::File;
use std::io::Write;

fn main() {
    let data = "Some data!";
    let mut f = File::create("/tmp/foo").expect("Unable to create file");
    f.write_all(data.as_bytes()).expect("Unable to write data");
}

Буферизованный ввод/вывод

Я почувствовал, что сообщество немного подтолкнуло использовать BufReader и BufWriter вместо чтения прямо из файла

Буферный считыватель (или записывающее устройство) использует буфер для уменьшения количества запросов ввода-вывода. Например, гораздо эффективнее получить доступ к диску один раз, чтобы прочитать 256 байтов, чем получить доступ к диску 256 раз.

При этом я не верю, что буферизованный читатель/писатель будет полезен при чтении всего файла. read_to_end, кажется, копирует данные в несколько больших кусках, поэтому передача может уже естественным образом объединиться в меньшее количество запросов ввода/вывода.

Вот пример использования его для чтения:

use std::fs::File;
use std::io::{BufReader, Read};

fn main() {
    let mut data = String::new();
    let f = File::open("/etc/hosts").expect("Unable to open file");
    let mut br = BufReader::new(f);
    br.read_to_string(&mut data).expect("Unable to read string");
    println!("{}", data);
}

И для записи:

use std::fs::File;
use std::io::{BufWriter, Write};

fn main() {
    let data = "Some data!";
    let f = File::create("/tmp/foo").expect("Unable to create file");
    let mut f = BufWriter::new(f);
    f.write_all(data.as_bytes()).expect("Unable to write data");
}

A BufReader более полезен, если вы хотите читать построчно:

use std::fs::File;
use std::io::{BufRead, BufReader};

fn main() {
    let f = File::open("/etc/hosts").expect("Unable to open file");
    let f = BufReader::new(f);

    for line in f.lines() {
        let line = line.expect("Unable to read line");
        println!("Line: {}", line);
    }
}