Wie kann man de facto Dateien in Rust 1.x lesen und schreiben?

136

Da Rust vergleichsweise neu ist, habe ich viel zu viele Möglichkeiten zum Lesen und Schreiben von Dateien gesehen. Viele sind extrem chaotische Schnipsel, die sich jemand für sein Blog ausgedacht hat, und 99% der Beispiele, die ich gefunden habe (sogar bei Stack Overflow), stammen von instabilen Builds, die nicht mehr funktionieren. Was ist nun, da Rust stabil ist, ein einfaches, lesbares, nicht in Panik geratenes Snippet zum Lesen oder Schreiben von Dateien?

Dies ist das Beste, was ich beim Lesen einer Textdatei erreicht habe, aber es wird immer noch nicht kompiliert, obwohl ich ziemlich sicher bin, dass ich alles aufgenommen habe, was ich haben sollte. Dies basiert auf einem Ausschnitt, den ich ausgerechnet auf Google+ gefunden habe, und das einzige, was ich geändert habe, ist, dass der alte BufferedReaderjetzt nur noch ist 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);
    }
}

Der Compiler beschwert sich:

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

Um es zusammenzufassen, was ich suche, ist:

  • Kürze
  • Lesbarkeit
  • deckt alle möglichen Fehler ab
  • keine Panik
Jared
quelle
Wie möchten Sie die Datei lesen? Möchten Sie es Zeile für Zeile, wie Sie gezeigt haben? Willst du alles in einer Saite? Es gibt mehr als eine Möglichkeit, "eine Datei zu lesen".
Shepmaster
Jede Art ist in Ordnung. Ich habe es absichtlich offen gelassen. Wenn alles in einer Zeichenfolge zusammengefasst ist, wäre es trivial, es in einen Vec <String> aufzuteilen und umgekehrt. An diesem Punkt meiner Suche nach Lösungen freue ich mich, nur den eleganten, aktuellen Rust-Datei-E / A-Code zu sehen, der funktioniert.
Jared
3
std::io::ReadBeachten Sie in Bezug auf den Merkmalsfehler ( ), dass Sie in Rust die Merkmale importieren müssen, die Sie voraussichtlich explizit verwenden werden . daher fehlt hier ein use std::io::Read(was ein sein könnte, use std::io::{Read,BufReader}um die beiden Verwendungen zusammen zu verschmelzen)
Matthieu M.

Antworten:

197

Keine der Funktionen, die ich hier zeige, gerät in Panik, aber ich verwende sie, expectweil ich nicht weiß, welche Art der Fehlerbehandlung am besten in Ihre Anwendung passt. Zum Lesen der Rost Programmiersprache ‚s Kapitel über die Fehlerbehandlung zu verstehen , wie in geeigneter Weise Fehler in Ihrem eigenen Programm zu handhaben .

Rost ab 1,26

Wenn Sie sich nicht um die zugrunde liegenden Details kümmern möchten, gibt es einzeilige Funktionen zum Lesen und Schreiben.

Lesen Sie eine Datei zu einem String

use std::fs;

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

Lesen Sie eine Datei als Vec<u8>

use std::fs;

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

Schreiben Sie eine Datei

use std::fs;

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

Rost 1.0 und höher

Diese Formulare sind etwas ausführlicher als die einzeiligen Funktionen, die ein Stringoder Vecfür Sie zuweisen , sind jedoch insofern leistungsfähiger, als Sie zugewiesene Daten wiederverwenden oder an ein vorhandenes Objekt anhängen können.

Daten lesen

Das Lesen einer Datei erfordert zwei Kernelemente: File und Read.

Lesen Sie eine Datei zu einem 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);
}

Lesen Sie eine Datei als 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());
}

Schreiben Sie eine Datei

Das Schreiben einer Datei ist ähnlich, außer dass wir das WriteMerkmal verwenden und immer Bytes ausschreiben. Sie können ein String/ &strin Bytes konvertieren mit 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");
}

Gepufferte E / A.

Ich fühlte mich von der Community ein wenig gedrängt, es zu verwenden BufReaderund BufWriteranstatt direkt aus einer Datei zu lesen

Ein gepufferter Leser (oder Schreiber) verwendet einen Puffer, um die Anzahl der E / A-Anforderungen zu verringern. Zum Beispiel ist es viel effizienter, einmal auf die Festplatte zuzugreifen, um 256 Bytes zu lesen, anstatt 256 Mal auf die Festplatte zuzugreifen.

Abgesehen davon glaube ich nicht, dass ein gepufferter Leser / Schreiber beim Lesen der gesamten Datei nützlich sein wird. read_to_endscheint Daten in etwas großen Blöcken zu kopieren, so dass die Übertragung möglicherweise bereits zu weniger E / A-Anforderungen zusammengeführt wird.

Hier ist ein Beispiel für die Verwendung zum Lesen:

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

Und zum Schreiben:

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 BufReaderist nützlicher, wenn Sie Zeile für Zeile lesen möchten:

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
quelle
2
Ich habe nicht wirklich viel, worauf ich mich stützen kann, aber als ich das recherchierte, fühlte ich mich von der Community ein wenig gedrängt, BufReader und BufWriter zu verwenden, anstatt direkt von einer Datei in eine Zeichenfolge zu lesen. Wissen Sie viel über diese Objekte oder die Vor- und Nachteile ihrer Verwendung gegenüber der "klassischeren" Version, die Sie in Ihrer Antwort gezeigt haben?
Jared
@ TheDaleks Ich folge deiner Frage nicht. b"foobar"ist ein Literal zum Erstellen eines Verweises auf ein Array von Bytes ( &[u8; N]). Als solches ist es unveränderlich. Es gibt nichts, was Sie nicht einfacher machen können.
Shepmaster
@Shepmaster Gelegentlich ist es vorteilhaft, ein Byte-Array anstelle einer codierten Zeichenfolge zu haben. Wenn Sie beispielsweise eine App erstellen möchten, die Dateien von einem Ort an einen anderen verschiebt, müssen Sie über die Rohbytes verfügen, damit Sie die von der App verarbeitbaren ausführbaren Dateien nicht beschädigen.
Die Daleks
@TheDaleks ja, deshalb erklärt diese Antwort, wie man a Vec<u8>zum Lesen und Schreiben verwendet. Das sind rohe Bytes.
Shepmaster