Apa cara de-facto membaca dan menulis file di Rust 1.x?

136

Dengan Rust yang relatif baru, saya telah melihat terlalu banyak cara membaca dan menulis file. Banyak cuplikan sangat berantakan yang dibuat seseorang untuk blog mereka, dan 99% contoh yang saya temukan (bahkan di Stack Overflow) berasal dari bangunan tidak stabil yang tidak lagi berfungsi. Sekarang Rust stabil, apa itu potongan sederhana, mudah dibaca, tidak panik untuk membaca atau menulis file?

Ini adalah hal terdekat yang saya dapatkan dengan sesuatu yang berfungsi dalam hal membaca file teks, tetapi masih belum kompilasi meskipun saya cukup yakin saya sudah memasukkan semua yang harus saya miliki. Ini didasarkan dari cuplikan yang saya temukan di Google+ dari semua tempat, dan satu-satunya hal yang saya ubah adalah bahwa yang lama BufferedReadersekarang hanya 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);
    }
}

Kompiler mengeluh:

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

Singkatnya, yang saya cari adalah:

  • keringkasan
  • keterbacaan
  • mencakup semua kemungkinan kesalahan
  • tidak panik
Jared
sumber
Bagaimana Anda ingin membaca file? Apakah Anda menginginkannya baris demi baris, seperti yang Anda tunjukkan? Apakah Anda ingin semuanya dalam satu string? Ada lebih dari satu cara untuk "membaca file".
Shepmaster
Apa pun caranya baik-baik saja. Saya sengaja membiarkannya terbuka. Jika itu dikumpulkan semua menjadi satu string, membaginya menjadi Vec <String> akan menjadi sepele, dan sebaliknya. Pada titik ini dalam pencarian saya untuk solusi, saya akan senang melihat kode I / O file Rust yang elegan dan terkini yang berfungsi.
Jared
3
Mengenai kesalahan sifat ( std::io::Read), perhatikan bahwa di Rust Anda harus mengimpor sifat-sifat yang Anda harapkan akan digunakan secara eksplisit ; jadi di sini Anda kehilangan sebuah use std::io::Read(yang bisa menjadi use std::io::{Read,BufReader}untuk menyatukan kedua kegunaan bersama)
Matthieu M.

Jawaban:

197

Tidak ada fungsi yang saya tunjukkan di sini panik sendiri, tetapi saya menggunakan expectkarena saya tidak tahu jenis penanganan kesalahan apa yang paling cocok dengan aplikasi Anda. Go membaca The Rust Bahasa Pemrograman 's bab tentang kesalahan penanganan untuk memahami cara tepat menangani kegagalan dalam program Anda sendiri.

Karat 1,26 dan seterusnya

Jika Anda tidak ingin peduli dengan detail yang mendasarinya, ada fungsi satu baris untuk membaca dan menulis.

Baca file ke String

use std::fs;

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

Baca file sebagai Vec<u8>

use std::fs;

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

Tulis file

use std::fs;

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

Karat 1.0 dan seterusnya

Bentuk-bentuk ini sedikit lebih verbose daripada fungsi satu-baris yang mengalokasikan Stringatau Vecuntuk Anda, tetapi lebih kuat karena Anda dapat menggunakan kembali data yang dialokasikan atau menambahkan ke objek yang ada.

Membaca data

Membaca file memerlukan dua bagian inti: Filedan Read.

Baca file ke 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);
}

Baca file sebagai 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());
}

Tulis file

Menulis file mirip, kecuali kami menggunakan Writesifat dan kami selalu menulis byte. Anda dapat mengonversi a String/ &strmenjadi byte dengan 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");
}

Buffered I / O

Saya merasa sedikit dorongan dari komunitas untuk menggunakan BufReaderdan BufWriterbukannya membaca langsung dari file

Pembaca yang dilindungi (atau penulis) menggunakan buffer untuk mengurangi jumlah permintaan I / O. Sebagai contoh, itu jauh lebih efisien untuk mengakses disk sekali untuk membaca 256 byte daripada mengakses disk 256 kali.

Yang sedang berkata, saya tidak percaya pembaca / penulis buffer akan berguna saat membaca seluruh file. read_to_endtampaknya menyalin data dalam potongan yang agak besar, sehingga transfer mungkin sudah secara alami digabungkan menjadi lebih sedikit permintaan I / O.

Berikut adalah contoh menggunakannya untuk membaca:

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

Dan untuk menulis:

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 BufReaderlebih berguna ketika Anda ingin membaca baris demi baris:

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
sumber
2
Saya tidak punya banyak hal untuk dijadikan dasar, tetapi ketika meneliti ini saya merasa sedikit dorongan dari komunitas untuk menggunakan BufReader dan BufWriter alih-alih membaca langsung dari file ke string. Apakah Anda tahu banyak tentang objek-objek ini atau pro dan kontra untuk menggunakannya pada versi "lebih klasik" yang Anda tunjukkan dalam jawaban Anda?
Jared
@TheDaleks Saya tidak mengikuti pertanyaan Anda. b"foobar"adalah literal untuk membuat referensi ke array byte ( &[u8; N]). Dengan demikian, itu tidak berubah. Tidak ada yang memberi Anda bahwa Anda tidak dapat melakukan dengan cara yang lebih sederhana.
Shepmaster
@Shepmaster Terkadang menguntungkan untuk memiliki byte-array daripada string yang disandikan; misalnya, jika Anda ingin membuat aplikasi yang memindahkan file dari satu tempat ke tempat lain, Anda harus memiliki byte mentah sehingga Anda tidak merusak file yang dapat dieksekusi yang diproses aplikasi.
The Daleks
@TheDaleks ya, itulah sebabnya jawaban ini menjelaskan cara menggunakan Vec<u8>untuk membaca dan menulis. Itu adalah byte mentah.
Shepmaster