Jaki jest de facto sposób czytania i zapisywania plików w Rust 1.x?

148

Ponieważ Rust jest stosunkowo nowy, widziałem zbyt wiele sposobów czytania i zapisywania plików. Wiele z nich to wyjątkowo niechlujne fragmenty, które ktoś wymyślił na swoim blogu, a 99% przykładów, które znalazłem (nawet w przypadku przepełnienia stosu) pochodzi z niestabilnych kompilacji, które już nie działają. Teraz, gdy Rust jest stabilny, jaki jest prosty, czytelny, niepanikujący fragment do odczytu lub zapisu plików?

Jest to najbliższe osiągnięcie czegoś, co działa pod względem odczytu pliku tekstowego, ale nadal nie kompiluje się, mimo że jestem prawie pewien, że uwzględniłem wszystko, co powinienem. Jest to oparte na fragmencie, który znalazłem w Google+ wszystkich miejsc, a jedyne, co zmieniłem, to to, że stary BufferedReaderjest teraz po prostu 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);
    }
}

Kompilator narzeka:

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() {
  |>                      ^^^^^

Podsumowując, szukam:

  • zwięzłość
  • czytelność
  • obejmuje wszystkie możliwe błędy
  • nie panikuje
Jared
źródło
Jak chcesz przeczytać plik? Czy chcesz, aby była to linia po linii, jak pokazałeś? Czy chcesz to wszystko w jednym ciągu? Istnieje więcej niż jeden sposób „odczytu pliku”.
Shepmaster
Każdy sposób jest w porządku. Celowo zostawiłem otwarte. Jeśli jest zebrany wszystko w jeden ciąg, podzielenie go na Vec <String> byłoby trywialne i na odwrót. Na tym etapie moich poszukiwań rozwiązań z przyjemnością zobaczę elegancki, aktualny kod I / O pliku Rusta, który działa.
Jared
3
Jeśli chodzi o błąd cechy ( std::io::Read), zwróć uwagę, że w Rust musisz zaimportować cechy, których zamierzasz używać jawnie ; więc tutaj brakuje use std::io::Read(które mogłoby być use std::io::{Read,BufReader}połączeniem dwóch zastosowań razem)
Matthieu M.

Odpowiedzi:

211

Żadna z funkcji, które tu pokazuję, sama nie panikuje, ale używam, expectponieważ nie wiem, jaki rodzaj obsługi błędów najlepiej pasuje do Twojej aplikacji. Go przeczytać rdza język programowania jest rozdział dotyczący obsługi błędów , aby zrozumieć, jak prawidłowo obsługiwać awarię w swoim własnym programie.

Rust 1.26 i nowsze

Jeśli nie chcesz przejmować się podstawowymi szczegółami, dostępne są jednowierszowe funkcje do czytania i pisania.

Przeczytaj plik do pliku String

use std::fs;

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

Przeczytaj plik jako plik Vec<u8>

use std::fs;

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

Napisz plik

use std::fs;

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

Rust 1.0 i nowsze

Formularze te są nieco bardziej szczegółowe niż funkcje jednowierszowe, które przydzielają Stringlub Vecdla Ciebie, ale mają większe możliwości, ponieważ można ponownie wykorzystać przydzielone dane lub dołączyć je do istniejącego obiektu.

Czytanie danych

Odczyt pliku wymaga dwóch podstawowych elementów: Filei Read.

Przeczytaj plik do pliku 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);
}

Przeczytaj plik jako plik 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());
}

Napisz plik

Pisanie pliku jest podobne, z tym że używamy Writecechy i zawsze wypisujemy bajty. Możesz przekonwertować String/ &strna bajty za pomocą 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");
}

Buforowane we / wy

Poczułem, że społeczność zachęciła mnie do korzystania BufReaderi BufWriterzamiast czytania bezpośrednio z pliku

Buforowany czytnik (lub program zapisujący) używa bufora w celu zmniejszenia liczby żądań we / wy. Na przykład o wiele bardziej efektywne jest jednorazowe uzyskanie dostępu do dysku w celu odczytania 256 bajtów zamiast dostępu do dysku 256 razy.

Biorąc to pod uwagę, nie sądzę, aby buforowany czytnik / pisarz był przydatny podczas czytania całego pliku. read_to_endwydaje się, że kopiuje dane w dość dużych porcjach, więc transfer może być już naturalnie połączony w mniejszą liczbę żądań we / wy.

Oto przykład użycia go do czytania:

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

A do pisania:

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 BufReaderjest bardziej przydatny, gdy chcesz czytać wiersz po wierszu:

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
źródło
2
Naprawdę nie mam wiele do oparcia, ale podczas badania tego poczułem nacisk ze strony społeczności, aby użyć BufReader i BufWriter zamiast czytać bezpośrednio z pliku do ciągu. Czy wiesz dużo o tych obiektach lub o zaletach i wadach ich używania w porównaniu z „bardziej klasyczną” wersją, którą przedstawiłeś w swojej odpowiedzi?
Jared
@TheDaleks Nie odpowiadam na twoje pytanie. b"foobar"jest literałem do tworzenia referencji do tablicy bajtów ( &[u8; N]). Jako taki jest niezmienny. Nie ma nic z tego, czego nie możesz zrobić w prostszy sposób.
Shepmaster
@Shepmaster Czasami korzystniejsze jest posiadanie tablicy bajtów zamiast zakodowanego ciągu; na przykład, jeśli chcesz stworzyć aplikację, która przenosi pliki z jednego miejsca do drugiego, musisz mieć nieprzetworzone bajty, aby nie uszkodzić plików wykonywalnych, które aplikacja przetwarza.
The Daleks
@TheDaleks tak, dlatego ta odpowiedź wyjaśnia, jak używać a Vec<u8>do czytania i pisania. To są surowe bajty.
Shepmaster