patternrustCritical
What's the de-facto way of reading and writing files in Rust 1.x?
Viewed 0 times
readingandfilesthewayfactorustwhatwriting
Problem
With Rust being comparatively new, I've seen far too many ways of reading and writing files. Many are extremely messy snippets someone came up with for their blog, and 99% of the examples I've found (even on Stack Overflow) are from unstable builds that no longer work. Now that Rust is stable, what is a simple, readable, non-panicking snippet for reading or writing files?
This is the closest I've gotten to something that works in terms of reading a text file, but it's still not compiling even though I'm fairly certain I've included everything I should have. This is based off of a snippet I found on Google+ of all places, and the only thing I've changed is that the old
The compiler complains:
To sum it up, what I'm looking for is:
This is the closest I've gotten to something that works in terms of reading a text file, but it's still not compiling even though I'm fairly certain I've included everything I should have. This is based off of a snippet I found on Google+ of all places, and the only thing I've changed is that the old
BufferedReader is now just 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);
}
}The compiler complains:
error: the trait bound std::result::Result: 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> in the current scope
--> src/main.rs:8:22
|>
8 |> for line in file.lines() {
|> ^^^^^
To sum it up, what I'm looking for is:
- brevity
- readability
- covers all possible errors
- doesn't panic
Solution
None of the functions I show here panic on their own, but I am using
Rust 1.26 and onwards
If you don't want to care about the underlying details, there are one-line functions for reading and writing.
Read a file to a
Read a file as a
Write a file
Rust 1.0 and onwards
These forms are slightly more verbose than the one-line functions that allocate a
Reading data
Reading a file requires two core pieces:
Read a file to a
Read a file as a
Write a file
Writing a file is similar, except we use the
Buffered I/O
I felt a bit of a push from the community to use
A buffered reader (or writer) uses a buffer to reduce the number of I/O requests. For example, it's much more efficient to access the disk once to read 256 bytes instead of accessing the disk 256 times.
That being said, I don't believe a buffered reader/writer will be useful when reading the entire file.
Here's an example of using it for reading:
And for writing:
A
expect because I don't know what kind of error handling will fit best into your application. Go read The Rust Programming Language's chapter on error handling to understand how to appropriately handle failure in your own program.Rust 1.26 and onwards
If you don't want to care about the underlying details, there are one-line functions for reading and writing.
Read a file to a
Stringuse std::fs;
fn main() {
let data = fs::read_to_string("/etc/hosts").expect("Should be able to read hosts file");
println!("{}", data);
}Read a file as a
Vecuse std::fs;
fn main() {
let data = fs::read("/etc/hosts").expect("Should be able to read hosts file");
println!("{}", data.len());
}Write a file
use std::fs;
fn main() {
let data = "Some data!";
fs::write("/tmp/foo", data).expect("Should be able to write to `/foo/tmp`");
}Rust 1.0 and onwards
These forms are slightly more verbose than the one-line functions that allocate a
String or Vec for you, but are more powerful in that you can reuse allocated data or append to an existing object.Reading data
Reading a file requires two core pieces:
File and Read.Read a file to a
Stringuse std::fs::File;
use std::io::Read;
fn main() {
let mut data = String::new();
let mut f = File::open("/etc/hosts").expect("Should be able to open `/etc/hosts`");
f.read_to_string(&mut data).expect("Should be able to read to string");
println!("{}", data);
}Read a file as a
Vecuse std::fs::File;
use std::io::Read;
fn main() {
let mut data = Vec::new();
let mut f = File::open("/etc/hosts").expect("Should be able to open `/etc/hosts`");
f.read_to_end(&mut data).expect("Should be able to read data");
println!("{}", data.len());
}Write a file
Writing a file is similar, except we use the
Write trait and we always write out bytes. You can convert a String / &str to bytes with as_bytes:use std::fs::File;
use std::io::Write;
fn main() {
let data = "Some data!";
let mut f = File::create("/tmp/foo").expect("Should be able to create file");
f.write_all(data.as_bytes()).expect("Should be able to write data");
}Buffered I/O
I felt a bit of a push from the community to use
BufReader and BufWriter instead of reading straight from a fileA buffered reader (or writer) uses a buffer to reduce the number of I/O requests. For example, it's much more efficient to access the disk once to read 256 bytes instead of accessing the disk 256 times.
That being said, I don't believe a buffered reader/writer will be useful when reading the entire file.
read_to_end seems to copy data in somewhat large chunks, so the transfer may already be naturally coalesced into fewer I/O requests.Here's an example of using it for reading:
use std::fs::File;
use std::io::{BufReader, Read};
fn main() {
let mut data = String::new();
let f = File::open("/etc/hosts").expect("Should be able to open `/etc/hosts`");
let mut br = BufReader::new(f);
br.read_to_string(&mut data).expect("Should be able to read to string");
println!("{}", data);
}And for writing:
use std::fs::File;
use std::io::{BufWriter, Write};
fn main() {
let data = "Some data!";
let f = File::create("/tmp/foo").expect("Should be able to create file");
let mut f = BufWriter::new(f);
f.write_all(data.as_bytes()).expect("Should be able to write data");
}A
BufReader is more useful when you want to read line-by-line:use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
let f = File::open("/etc/hosts").expect("Should be able to open `/etc/hosts`");
let f = BufReader::new(f);
for line in f.lines() {
let line = line.expect("Should be able to read line");
println!("Line: {}", line);
}
}Code Snippets
use std::fs;
fn main() {
let data = fs::read_to_string("/etc/hosts").expect("Should be able to read hosts file");
println!("{}", data);
}use std::fs;
fn main() {
let data = fs::read("/etc/hosts").expect("Should be able to read hosts file");
println!("{}", data.len());
}use std::fs;
fn main() {
let data = "Some data!";
fs::write("/tmp/foo", data).expect("Should be able to write to `/foo/tmp`");
}use std::fs::File;
use std::io::Read;
fn main() {
let mut data = String::new();
let mut f = File::open("/etc/hosts").expect("Should be able to open `/etc/hosts`");
f.read_to_string(&mut data).expect("Should be able to read to string");
println!("{}", data);
}use std::fs::File;
use std::io::Read;
fn main() {
let mut data = Vec::new();
let mut f = File::open("/etc/hosts").expect("Should be able to open `/etc/hosts`");
f.read_to_end(&mut data).expect("Should be able to read data");
println!("{}", data.len());
}Context
Stack Overflow Q#31192956, score: 490
Revisions (0)
No revisions yet.