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


136

Оскільки Руст був порівняно новим, я бачив занадто багато способів читання та запису файлів. Багато з них є надзвичайно брудними фрагментами, які хтось придумав для свого блогу, і 99% прикладів, які я знайшов (навіть на переповнення стека), є з нестабільних конструкцій, які більше не працюють. Тепер, коли 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

Будь-який спосіб - це добре. Я залишив це відкрито навмисно. Якщо все зібрано в один рядок, розділення його на Vec <String> було б тривіальним, і навпаки. У цей момент мого пошуку рішень я з радістю побачу елегантний, сучасний код вводу / виводу файлу Rust, який працює.
Джаред

3
Щодо помилки ознаки ( std::io::Read), зауважте, що в Rust ви повинні імпортувати риси, які ви очікуєте використовувати явно ; таким чином, тут вам не вистачає use std::io::Read(що могло б use std::io::{Read,BufReader}поєднати два використання разом)
Матьє М.

Відповіді:


197

Жодна з функцій, які я показую тут, не панікує сама по собі, але я використовую, expectтому що не знаю, який тип помилок найкраще впишеться у вашу програму. Перейдіть до розділу Програми іржі про мову щодо помилок, щоб зрозуміти, як правильно впоратися з помилками у власній програмі.

Іржа 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");
}

Іржа 1,0 і далі

Ці форми трохи більш багатослівні, ніж однолінійні функції, які виділяють a 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");
}

А 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);
    }
}

2
У мене насправді не так багато, щоб базувати це, але, вивчаючи це, я відчув трохи поштовху від спільноти використовувати BufReader і BufWriter, а не читати прямо з файлу в рядок. Чи знаєте ви багато про ці об'єкти або про плюси і мінуси їх використання в "більш класичній" версії, яку ви показали у своїй відповіді?
Джаред

@TheDaleks Я не стежу за вашим запитанням. b"foobar"є буквальним для створення посилання на масив байтів ( &[u8; N]). Як такий, він непорушний. Немає нічого, що ви не можете зробити простішим способом.
Шепмайстер

@Shepmaster Іноді вигідно мати масив байтів замість кодованого рядка; наприклад, якщо ви хочете зробити додаток, який переміщує файли з одного місця на інше, вам потрібно мати необроблені байти, щоб ви не пошкоджували виконувані файли, які обробляє додаток.
Далекс

@TheDaleks так, саме тому ця відповідь пояснює, як використовувати Vec<u8>для читання та письма. Це сирі байти.
Шепмайстер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.