2015-06-12 18 views
13

Voglio leggere un file e recuperare un vettore di String s. La seguente funzione funziona, ma esiste un modo più conciso o idiomatico?Leggere un file e ottenere una serie di stringhe

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

fn lines_from_file(filename: &str) -> Vec<String> { 
    let mut file = match File::open(filename) { 
     Ok(file) => file, 
     Err(_) => panic!("no such file"), 
    }; 
    let mut file_contents = String::new(); 
    file.read_to_string(&mut file_contents) 
     .ok() 
     .expect("failed to read!"); 
    let lines: Vec<String> = file_contents.split("\n") 
     .map(|s: &str| s.to_string()) 
     .collect(); 
    lines 
} 

Alcune cose che sembrano non ottimale per me:

  • Due controlli di errore separati per la lettura del file.
  • Lettura dell'intero file a String, che verrà gettato via. Ciò sarebbe particolarmente dispendioso se volessi solo le prime linee N.
  • Fare un &str per riga, che verrà gettato via, invece di andare direttamente dal file a un numero String per riga.

Come può essere migliorato?

+3

usare 'linee()' iteratore: http://doc.rust-lang.org/std/io/trait.BufRead.html#method.lines – BurntSushi5

+0

non capisco il motivo per cui questa domanda è stata downvoted. Se è considerato troppo soggettivo, propongo di rimuovere il tag 'idiomatic', poiché descrive esattamente il tipo di cosa che sto chiedendo. –

risposta

11

Come BurntSushi said, è possibile utilizzare solo the lines() iterator. Ma, per rispondere alla sua domanda così com'è:

  • Probabilmente si dovrebbe leggere Error Handling in Rust; quelli unwrap() devono essere convertiti in try! s, con il risultato della funzione che diventa Result<Vec<String>, E> per un ragionevole E.

  • Ancora, lines() iteratore. L'altra cosa che puoi fare è leggere l'intero file in un Stringe restituirlo; c'è a lines() iterator for strings as well.

  • Questo non si può fare nulla circa: file_contents possiede il suo contenuto, e non è possibile dividerli in più, di proprietà String s. L'unica cosa che puoi fare è prendere in prestito il contenuto di ogni riga, quindi convertirlo in un nuovo String. Detto questo, il modo in cui hai messo questo implica che tu credi che la creazione di uno &str sia costosa; non lo è. È letteralmente solo calcolare una coppia di offset e restituirle. Una slice &str equivale effettivamente a (*const u8, usize).

Ecco una versione modificata che fa sostanzialmente la stessa cosa:

use std::io::{self, BufRead}; 
use std::path::Path; 
use std::fs::File; 

fn lines_from_file<P>(filename: P) -> Result<io::Lines<io::BufReader<File>>, io::Error> 
where 
    P: AsRef<Path>, 
{ 
    let mut file = try!(File::open(filename)); 
    Ok(io::BufReader::new(file).lines()) 
} 

Un altro cambiamento che ho fatto: filename è ora un generico P: AsRef<Path>, perché è quello che File::open vuole, in modo da accettare più tipi senza bisogno di conversione.

+0

Neat! Ma non restituire un risultato implica che se il file è illeggibile, sarà il problema del chiamante? Forse non c'è modo di evitarlo mentre sei anche pigro nel leggere il file? –

+3

@NathanLong L'uso di 'unwrap' o' panic! 'Significa che se il file è illeggibile, l'intero thread esplode e il chiamante muore senza preavviso. Se il chiamante non si preoccupa di questo, può semplicemente chiamare "scartare" sul risultato e ottenere lo stesso comportamento di esplosione. * Oppure * possono effettivamente decidere cosa fare con l'errore. In entrambi i casi, non ha alcun impatto sulla lettura del file: entrambi causeranno l'arresto della funzione in un modo o nell'altro. –

5

DK.'s answer ha perfettamente ragione e ha un'ottima spiegazione. Tuttavia, lei ha affermato:

leggere un file e ottenere un array di stringhe

array Rust hanno una lunghezza fissa, noto al momento della compilazione, quindi immagino che realmente significa "vettore".Vorrei scrivere in questo modo:

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

fn lines_from_file<P>(filename: P) -> Vec<String> 
where 
    P: AsRef<Path>, 
{ 
    let file = File::open(filename).expect("no such file"); 
    let buf = BufReader::new(file); 
    buf.lines() 
     .map(|l| l.expect("Could not parse line")) 
     .collect() 
} 

// --- 

fn main() { 
    let lines = lines_from_file("/etc/hosts"); 
    for line in lines { 
     println!("{:?}", line); 
    } 
} 
  1. Come in altra risposta, ne vale la pena di utilizzare il tipo generico per il nome del file.
  2. Result::expect accorcia il panico su Err.
  3. BufRead::lines gestisce più tipi di newline, non solo "\n".
  4. BufRead::lines fornisce inoltre String s separatamente, invece di un grande glob.
  5. Non c'è motivo di raccogliere una variabile temporanea solo per restituirla. Non c'è in particolare alcun motivo per ripetere il tipo (Vec<String>).
Problemi correlati