Quelle est la manière de facto de lire et d'écrire des fichiers dans Rust 1.x?

136

Avec Rust étant relativement nouveau, j'ai vu beaucoup trop de façons de lire et d'écrire des fichiers. Beaucoup sont des extraits extrêmement désordonnés que quelqu'un a inventés pour leur blog, et 99% des exemples que j'ai trouvés (même sur Stack Overflow) proviennent de versions instables qui ne fonctionnent plus. Maintenant que Rust est stable, qu'est-ce qu'un extrait de code simple, lisible et sans panique pour lire ou écrire des fichiers?

C'est ce qui se rapproche le plus de quelque chose qui fonctionne en termes de lecture d'un fichier texte, mais il n'est toujours pas compilé même si je suis assez certain d'avoir inclus tout ce que je devrais avoir. Ceci est basé sur un extrait que j'ai trouvé sur Google+ de tous les lieux, et la seule chose que j'ai changé est que l'ancien BufferedReaderest maintenant juste 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);
    }
}

Le compilateur se plaint:

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

Pour résumer, ce que je recherche c'est:

  • brièveté
  • lisibilité
  • couvre toutes les erreurs possibles
  • ne panique pas
Jared
la source
Comment voulez-vous lire le fichier? Le voulez-vous ligne par ligne, comme vous l'avez montré? Voulez-vous tout dans une seule chaîne? Il existe plusieurs façons de «lire un fichier».
Shepmaster
L'une ou l'autre manière est bien. Je l'ai laissé ouvert intentionnellement. S'il est rassemblé en une seule chaîne, le fractionner en une <String> Vec serait trivial, et vice versa. À ce stade de ma recherche de solutions, je serai heureux de voir un code d'E / S de fichier Rust élégant et à jour qui fonctionne.
Jared
3
En ce qui concerne l'erreur de trait ( std::io::Read), notez que dans Rust, vous devez importer les traits que vous prévoyez d'utiliser explicitement ; il vous manque donc ici un use std::io::Read(qui pourrait être un use std::io::{Read,BufReader}pour fusionner les deux utilisations ensemble)
Matthieu M.

Réponses:

197

Aucune des fonctions que je montre ici ne panique d'elles-mêmes, mais j'utilise expectparce que je ne sais pas quel type de gestion des erreurs conviendra le mieux à votre application. Allez lire la rouille Langage de programmation de » chapitre sur la gestion des erreurs pour comprendre comment gérer correctement l' échec dans votre propre programme.

Rust 1.26 et plus

Si vous ne voulez pas vous soucier des détails sous-jacents, il existe des fonctions sur une ligne pour la lecture et l'écriture.

Lire un fichier dans un String

use std::fs;

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

Lire un fichier en tant que Vec<u8>

use std::fs;

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

Ecrire un fichier

use std::fs;

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

Rust 1.0 et versions ultérieures

Ces formulaires sont légèrement plus détaillés que les fonctions sur une ligne qui allouent un String ouVec pour vous, mais sont plus puissants en ce sens que vous pouvez réutiliser des données allouées ou les ajouter à un objet existant.

Lecture des données

La lecture d'un fichier nécessite deux éléments essentiels: FileetRead .

Lire un fichier dans 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);
}

Lire un fichier en tant que 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());
}

Ecrire un fichier

L'écriture d'un fichier est similaire, sauf que nous utilisons le Writetrait et que nous écrivons toujours des octets. Vous pouvez convertir un String/ &stren octets avec 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 tamponnées

Je me suis senti un peu poussé par la communauté à utiliser BufReaderet BufWriterau lieu de lire directement à partir d'un fichier

Un lecteur (ou enregistreur) tamponné utilise un tampon pour réduire le nombre de demandes d'E / S. Par exemple, il est beaucoup plus efficace d'accéder au disque une fois pour lire 256 octets au lieu d'accéder au disque 256 fois.

Cela étant dit, je ne crois pas qu'un lecteur / écrivain tamponné sera utile lors de la lecture du fichier entier. read_to_endsemble copier les données en morceaux assez volumineux, de sorte que le transfert peut déjà être naturellement fusionné en moins de demandes d'E / S.

Voici un exemple d'utilisation pour la lecture:

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

Et pour écrire:

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 BufReaderest plus utile lorsque vous souhaitez lire ligne par ligne:

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
la source
2
Je n'ai pas vraiment grand chose sur quoi baser cela, mais en recherchant cela, j'ai ressenti un peu de pression de la communauté pour utiliser BufReader et BufWriter au lieu de lire directement d'un fichier à une chaîne. En savez-vous beaucoup sur ces objets ou sur les avantages et les inconvénients de leur utilisation par rapport à la version "plus classique" que vous avez montrée dans votre réponse?
Jared
@TheDaleks Je ne suis pas votre question. b"foobar"est un littéral pour créer une référence à un tableau d'octets ( &[u8; N]). En tant que tel, il est immuable. Il ne vous donne rien que vous ne puissiez pas faire de manière plus simple.
Shepmaster
@Shepmaster Parfois, il est avantageux d'avoir un tableau d'octets au lieu d'une chaîne codée; par exemple, si vous souhaitez créer une application qui déplace des fichiers d'un endroit à un autre, vous devez disposer des octets bruts afin de ne pas corrompre les fichiers exécutables traités par l'application.
The Daleks
@TheDaleks oui, c'est pourquoi cette réponse explique comment utiliser a Vec<u8>pour lire et écrire. Ce sont des octets bruts.
Shepmaster le