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

136

Поскольку 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() {
  |>                      ^^^^^

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

  • краткость
  • читабельность
  • охватывает все возможные ошибки
  • не паникует
Джаред
источник
Как вы хотите прочитать файл? Вы хотите, чтобы это было построчно, как вы показали? Вы хотите все в одной строке? Есть несколько способов «прочитать файл».
Shepmaster 02
В любом случае все в порядке. Я оставил его открытым намеренно. Если собрать все в одну строку, разбить ее на Vec <String> будет тривиально, и наоборот. На данном этапе поиска решений я буду рад просто увидеть элегантный, актуальный код ввода-вывода файлов Rust, который работает.
Джаред
3
Что касается трейта error ( std::io::Read), обратите внимание, что в Rust вы должны явно импортировать трейты, которые вы ожидаете использовать ; таким образом, здесь вам не хватает use std::io::Read(который может быть use std::io::{Read,BufReader}объединением двух использований вместе)
Matthieu M.

Ответы:

197

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

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);
    }
}
Shepmaster
источник
2
На самом деле мне не на чем основывать это, но, исследуя это, я почувствовал небольшой толчок со стороны сообщества использовать BufReader и BufWriter вместо чтения прямо из файла в строку. Вы много знаете об этих объектах или о плюсах и минусах их использования по сравнению с "более классической" версией, которую вы указали в своем ответе?
Джаред
@TheDaleks Я не слежу за вашим вопросом. b"foobar"- это литерал для создания ссылки на массив bytes ( &[u8; N]). Таким образом, он неизменен. Нет ничего из того, что нельзя было бы сделать проще.
Shepmaster
@Shepmaster Иногда бывает полезно иметь массив байтов вместо закодированной строки; например, если вы хотите создать приложение, которое перемещает файлы из одного места в другое, вам нужны необработанные байты, чтобы не повредить исполняемые файлы, обрабатываемые приложением.
The Daleks,
@TheDaleks да, поэтому этот ответ объясняет, как использовать a Vec<u8>для чтения и записи. Это необработанные байты.
Shepmaster