Qual è il modo di fatto di leggere e scrivere file in Rust 1.x?


136

Con Rust relativamente nuovo, ho visto troppi modi di leggere e scrivere file. Molti sono frammenti estremamente disordinati che qualcuno ha inventato per il loro blog e il 99% degli esempi che ho trovato (anche su Stack Overflow) provengono da build instabili che non funzionano più. Ora che Rust è stabile, cos'è uno snippet semplice, leggibile e senza panico per leggere o scrivere file?

Questo è il più vicino che ho ottenuto a qualcosa che funziona in termini di lettura di un file di testo, ma non si sta ancora compilando anche se sono abbastanza sicuro di aver incluso tutto ciò che avrei dovuto. Questo si basa su uno snippet che ho trovato su Google+ in tutti i luoghi e l'unica cosa che ho cambiato è che il vecchio BufferedReaderora è solo 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);
    }
}

Il compilatore si lamenta:

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

Per riassumere, quello che sto cercando è:

  • concisione
  • leggibilità
  • copre tutti i possibili errori
  • non fatevi prendere dal panico

Come vuoi leggere il file? Lo vuoi riga per riga, come hai mostrato? Vuoi tutto in una stringa? Esiste più di un modo per "leggere un file".
Shepmaster,

Ad ogni modo va bene. L'ho lasciato intenzionalmente aperto. Se viene raccolto tutto in una stringa, dividerlo in un Vec <String> sarebbe banale e viceversa. A questo punto nella mia ricerca di soluzioni, sarò felice di vedere il codice I / O del file Rust elegante e aggiornato che funziona.
Jared,

3
Per quanto riguarda l'errore tratto ( std::io::Read), notare che in Rust è necessario importare i tratti che si prevede di utilizzare esplicitamente ; quindi qui ti manca un use std::io::Read(che potrebbe essere un use std::io::{Read,BufReader}per fondere insieme i due usi)
Matthieu M.

Risposte:


197

Nessuna delle funzioni che mostro qui va nel panico da sola, ma sto usando expectperché non so quale tipo di gestione degli errori si adatterà meglio alla tua applicazione. Vai a leggere il capitolo del linguaggio di programmazione Rust sulla gestione degli errori per capire come gestire correttamente i guasti nel tuo programma.

Ruggine 1,26 e successive

Se non vuoi preoccuparti dei dettagli sottostanti, ci sono funzioni di una riga per la lettura e la scrittura.

Leggi un file su a String

use std::fs;

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

Leggi un file come a Vec<u8>

use std::fs;

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

Scrivi un file

use std::fs;

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

Rust 1.0 e versioni successive

Questi moduli sono leggermente più dettagliati delle funzioni a riga singola che allocano un Stringo Vecper te, ma sono più potenti in quanto puoi riutilizzare i dati allocati o accodarli a un oggetto esistente.

Lettura dei dati

La lettura di un file richiede due pezzi fondamentali: Filee Read.

Leggi un file su a 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);
}

Leggi un file come a 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());
}

Scrivi un file

Scrivere un file è simile, tranne per il fatto che usiamo il Writetratto e scriviamo sempre byte. È possibile convertire un String/ &strin byte con 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");
}

I / O bufferizzato

Ho sentito un po 'di spinta da parte della community da usare BufReadere BufWriterinvece di leggere direttamente da un file

Un lettore (o scrittore) con buffer utilizza un buffer per ridurre il numero di richieste I / O. Ad esempio, è molto più efficiente accedere al disco una volta per leggere 256 byte invece di accedere al disco 256 volte.

Detto questo, non credo che un lettore / scrittore con buffer sarà utile durante la lettura dell'intero file. read_to_endsembra copiare i dati in blocchi piuttosto grandi, quindi il trasferimento potrebbe già essere naturalmente riunito in un minor numero di richieste I / O.

Ecco un esempio di come usarlo per la lettura:

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

E per scrivere:

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è più utile quando si desidera leggere riga per riga:

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
Non ho molto su cui basare questo, ma durante la ricerca di questo mi sono sentito un po 'spinto dalla comunità a usare BufReader e BufWriter invece di leggere direttamente da un file a una stringa. Sai molto di questi oggetti o dei pro e contro del loro utilizzo rispetto alla versione "più classica" che hai mostrato nella tua risposta?
Jared,

@TheDaleks Non sto seguendo la tua domanda. b"foobar"è un valore letterale per creare un riferimento a una matrice di byte ( &[u8; N]). Come tale, è immutabile. Non c'è niente che ti dia che non puoi fare in un modo più semplice.
Shepmaster il

@Shepmaster Occasionalmente è vantaggioso disporre di un array di byte anziché di una stringa codificata; ad esempio, se si desidera creare un'app che sposta i file da un punto all'altro, è necessario disporre dei byte non elaborati in modo da non danneggiare i file eseguibili elaborati dall'app.
The Daleks,

@TheDaleks sì, ecco perché questa risposta spiega come usare a Vec<u8>per leggere e scrivere. Quelli sono byte grezzi.
Shepmaster
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.