¿Cuál es la forma de facto de leer y escribir archivos en Rust 1.x?

136

Con Rust siendo relativamente nuevo, he visto demasiadas formas de leer y escribir archivos. Muchos son fragmentos extremadamente desordenados que alguien ideó para su blog, y el 99% de los ejemplos que he encontrado (incluso en Stack Overflow) son de compilaciones inestables que ya no funcionan. Ahora que Rust es estable, ¿qué es un fragmento simple, legible y sin pánico para leer o escribir archivos?

Esto es lo más cerca que he estado de algo que funciona en términos de leer un archivo de texto, pero todavía no se está compilando, aunque estoy bastante seguro de que he incluido todo lo que debería tener. Esto se basa en un fragmento que encontré en Google+ de todos los lugares, y lo único que he cambiado es que el viejo BufferedReaderahora es 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);
    }
}

El compilador se queja:

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

Para resumir, lo que estoy buscando es:

  • brevedad
  • legibilidad
  • cubre todos los posibles errores
  • no se asusta
Jared
fuente
¿Cómo quieres leer el archivo? ¿Lo quieres línea por línea, como has mostrado? ¿Lo quieres todo en una cuerda? Hay más de una forma de "leer un archivo".
Shepmaster
De cualquier manera está bien. Lo dejé abierto intencionalmente. Si se recopila todo en una cadena, dividirlo en Vec <String> sería trivial, y viceversa. En este punto de mi búsqueda de soluciones, estaré feliz de ver un código de E / S de archivo Rust elegante y actualizado que funcione.
Jared
3
Con respecto al error de rasgo ( std::io::Read), tenga en cuenta que en Rust debe importar los rasgos que espera utilizar explícitamente ; por lo tanto, aquí te falta un use std::io::Read(que podría ser un use std::io::{Read,BufReader}para unir los dos usos juntos)
Matthieu M.

Respuestas:

197

Ninguna de las funciones que muestro aquí entra en pánico por sí sola, pero la estoy usando expectporque no sé qué tipo de manejo de errores se ajustará mejor a su aplicación. Ve y lee el óxido Lenguaje de programación 's capítulo sobre el manejo de errores para entender cómo manejar adecuadamente el fracaso en su propio programa.

Óxido 1.26 y en adelante

Si no desea preocuparse por los detalles subyacentes, hay funciones de una línea para leer y escribir.

Leer un archivo a un String

use std::fs;

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

Leer un archivo como Vec<u8>

use std::fs;

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

Escribir un archivo

use std::fs;

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

Rust 1.0 y en adelante

Estas formas son un poco más detalladas que las funciones de una línea que asignan un Stringo Vecpara usted, pero son más poderosas ya que puede reutilizar los datos asignados o agregarlos a un objeto existente.

Lectura de datos

Leer un archivo requiere dos piezas centrales: Filey Read.

Leer un archivo a un 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);
}

Leer un archivo como 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());
}

Escribir un archivo

Escribir un archivo es similar, excepto que usamos el Writerasgo y siempre escribimos bytes. Puede convertir a String/ &stra bytes 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");
}

E / S almacenadas

Sentí un pequeño empujón de la comunidad para usar BufReadery en BufWriterlugar de leer directamente de un archivo

Un lector (o escritor) con búfer utiliza un búfer para reducir la cantidad de solicitudes de E / S. Por ejemplo, es mucho más eficiente acceder al disco una vez para leer 256 bytes en lugar de acceder al disco 256 veces.

Dicho esto, no creo que un lector / escritor protegido sea útil al leer el archivo completo. read_to_endparece copiar datos en fragmentos algo grandes, por lo que la transferencia puede estar naturalmente unida en menos solicitudes de E / S.

Aquí hay un ejemplo de cómo usarlo para leer:

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

Y para escribir:

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 BufReaderes más útil cuando quieres leer línea por línea:

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
fuente
2
Realmente no tengo mucho en qué basar esto, pero mientras investigaba esto, sentí un pequeño empujón de la comunidad para usar BufReader y BufWriter en lugar de leer directamente de un archivo a una cadena. ¿Sabe mucho acerca de estos objetos o las ventajas y desventajas de usarlos en la versión "más clásica" que ha mostrado en su respuesta?
Jared
@TheDaleks No estoy siguiendo tu pregunta. b"foobar"es un literal para crear una referencia a una matriz de bytes ( &[u8; N]). Como tal, es inmutable. No hay nada que te dé que no puedas hacer de una manera más simple.
Shepmaster
@Shepmaster Ocasionalmente es ventajoso tener una matriz de bytes en lugar de una cadena codificada; por ejemplo, si desea crear una aplicación que mueva archivos de un lugar a otro, debe tener los bytes sin procesar para que no corrompa los archivos ejecutables que procesa la aplicación.
Los Daleks
@TheDaleks sí, por eso esta respuesta explica cómo usar un Vec<u8>para leer y escribir. Esos son bytes sin procesar.
Shepmaster