I am trying to learn basic Rust. Is this a good way to get the contents of a file? Am I handling errors the best way? Is there any way this could be more performant?
use std::fs::File;
use std::io::Read;
fn main() {
let path = "bar/foo.txt";
match read(path) {
Ok(contents) => println!("{}", contents),
Err(err) => println!("Unable to read '{}': {}", path, err),
}
}
fn read(file: &str) -> Result<String, std::io::Error> {
let mut file = match File::open(file) {
Ok(mut f) => f,
Err(err) => return Err(err),
};
let mut data = String::new();
match file.read_to_string(&mut data) {
Ok(_) => return Ok(data),
Err(err) => return Err(err),
}
}
1 Answer 1
Your code gives a warning:
warning: variable does not need to be mutable --> src/main.rs:15:12 | 15 | Ok(mut f) => f, | ^^^^^ |
When you move a value from one binding to another, the two bindings don't have to agree on mutability. It may sound strange, but it's valid to move from an immutable binding to a mutable binding. It's perfectly safe because in order to be able to move a value, there must not be any pointers to it, which ensures that you have exclusive access to the value. Therefore, you could write
Ok(f)
here instead ofOk(mut f)
.The
match / return Err
pattern is so frequent that Rust has a shorthand for it: the?
operator (and its predecessor, thetry!
macro). We could rewrite your program using the?
operator like this:use std::fs::File; use std::io::Read; fn main() { let path = "bar/foo.txt"; match read(path) { Ok(contents) => println!("{}", contents), Err(err) => println!("Unable to read '{}': {}", path, err), } } fn read(file: &str) -> Result<String, std::io::Error> { let mut file = File::open(file)?; let mut data = String::new(); file.read_to_string(&mut data)?; Ok(data) }
The second
match
inread
ends the function, so we don't need the?
operator. Instead, we could usemap
to replace the data in theOk
variant while keeping theErr
the same, and then just return the result ofmap
.fn read(file: &str) -> Result<String, std::io::Error> { let mut file = File::open(file)?; let mut data = String::new(); file.read_to_string(&mut data).map(|_| data) }
Which one you use is up to you. :)
File::open
accepts more than just&str
. Its signature is:fn open<P: AsRef<Path>>(path: P) -> Result<File>
You could make your own
read
function more generic by introducing a type parameter.use std::path::Path; fn read<P: AsRef<Path>>(file: P) -> Result<String, std::io::Error> { let mut file = File::open(file)?; let mut data = String::new(); file.read_to_string(&mut data)?; Ok(data) }
Look at the list of implementations for
AsRef<Path>
to see what types you can now pass to your function.
-
\$\begingroup\$ The reason for this apparently overcomplicated string handling is that filenames aren't required to be valid unicode while
String
/&str
require valid unicode. \$\endgroup\$CodesInChaos– CodesInChaos2017年12月20日 17:04:27 +00:00Commented Dec 20, 2017 at 17:04