Qual é a maneira de fato de ler e gravar arquivos no Rust 1.x?

136

Com o Rust sendo relativamente novo, já vi muitas maneiras de ler e gravar arquivos. Muitos são trechos extremamente confusos que alguém criou para seu blog, e 99% dos exemplos que encontrei (mesmo no Stack Overflow) são de versões instáveis ​​que não funcionam mais. Agora que o Rust está estável, o que é um snippet simples, legível e sem pânico para ler ou gravar arquivos?

Este é o mais próximo que cheguei de algo que funciona em termos de leitura de um arquivo de texto, mas ainda não está compilando, mesmo tendo certeza de que incluí tudo o que deveria ter. Isso se baseia em um snippet que encontrei no Google+ de todos os lugares, e a única coisa que mudei é que o antigo BufferedReaderagora é apenas 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);
    }
}

O compilador reclama:

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, o que estou procurando é:

  • brevidade
  • legibilidade
  • cobre todos os erros possíveis
  • não entra em pânico
Jared
fonte
Como você deseja ler o arquivo? Deseja linha por linha, como você mostrou? Você quer tudo em uma string? Há mais de uma maneira de "ler um arquivo".
Shepmaster
De qualquer maneira, tudo bem. Deixei intencionalmente aberto. Se ele for coletado em uma única seqüência, dividi-la em um Vec <> será trivial e vice-versa. Nesse momento, em minha busca por soluções, ficarei feliz em ver o código de E / S elegante e atualizado do arquivo Rust que funciona.
Jared
3
Com relação ao erro de característica ( std::io::Read), observe que no Rust você deve importar as características que espera usar explicitamente ; Assim, aqui você está faltando um use std::io::Read(o que poderia ser um use std::io::{Read,BufReader}a se aglutinar os dois usos em conjunto)
Matthieu M.

Respostas:

197

Nenhuma das funções que mostro aqui entra em pânico por conta própria, mas estou usando expectporque não sei que tipo de tratamento de erros se encaixará melhor em seu aplicativo. Leia o capítulo da Linguagem de programação Rust sobre tratamento de erros para entender como lidar adequadamente com falhas em seu próprio programa.

Ferrugem 1.26 em diante

Se você não deseja se preocupar com os detalhes subjacentes, existem funções de uma linha para leitura e gravação.

Leia um arquivo para um String

use std::fs;

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

Leia um arquivo como Vec<u8>

use std::fs;

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

Escreva um arquivo

use std::fs;

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

Rust 1.0 em diante

Esses formulários são um pouco mais detalhados do que as funções de uma linha que alocam um Stringou Vecpara você, mas são mais poderosas, pois você pode reutilizar dados alocados ou anexar a um objeto existente.

Lendo dados

A leitura de um arquivo requer duas partes principais: FileeRead .

Leia um arquivo para um 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);
}

Leia um arquivo 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());
}

Escreva um arquivo

Escrever um arquivo é semelhante, exceto que usamos a Writecaracterística e sempre escrevemos bytes. Você pode converter um String/ &strpara bytes com 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 com buffer

Eu senti um pouco da comunidade para usar BufReadere, em BufWritervez de ler diretamente de um arquivo

Um leitor (ou gravador) em buffer usa um buffer para reduzir o número de solicitações de E / S. Por exemplo, é muito mais eficiente acessar o disco uma vez para ler 256 bytes em vez de acessar o disco 256 vezes.

Dito isto, não acredito que um leitor / gravador em buffer seja útil ao ler o arquivo inteiro. read_to_endparece copiar dados em partes um pouco grandes, portanto a transferência já pode ser naturalmente combinada em menos solicitações de E / S.

Aqui está um exemplo de como usá-lo para leitura:

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 para escrever:

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é mais útil quando você deseja ler linha por linha:

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
fonte
2
Eu realmente não tenho muito o que basear nisso, mas ao pesquisar isso, senti um pouco da comunidade para usar o BufReader e o BufWriter em vez de ler diretamente de um arquivo para uma string. Você sabe muito sobre esses objetos ou os prós e contras de usá-los na versão "mais clássica" que você mostrou em sua resposta?
Jared
@TheDaleks Não estou seguindo sua pergunta. b"foobar"é um literal para criar uma referência a uma matriz de bytes ( &[u8; N]). Como tal, é imutável. Não há nada que você não possa fazer de uma maneira mais simples.
Shepmaster 6/06
@Shepmaster Ocasionalmente, é vantajoso ter uma matriz de bytes em vez de uma string codificada; por exemplo, se você deseja criar um aplicativo que mova arquivos de um ponto para outro, é necessário ter os bytes brutos para não corromper os arquivos executáveis ​​processados ​​pelo aplicativo.
The Daleks
@ TheDaleks sim, é por isso que esta resposta explica como usar um Vec<u8>para leitura e escrita. Esses são bytes brutos.
Shepmaster 06/06