try!
is a macro that returns Err
s automatically; ?
is syntax that does mostly the same thing, but it works with any type that implements the Try
trait.
As of Rust 1.22.0, Option
implements Try
, so it can be used with ?
. Before that, ?
could only be used in functions that return a Result
. try!
continues to only work with Result
s.
As of Rust 1.26.0, main
is allowed to return a value that implements Termination
. Before that, it doesn’t return any value.
As of Rust 1.26.0
Your original code works if you mark main
as returning a Result
and then return Ok(())
in all the “success” cases:
use std::{fs, io, path::Path};
fn main() -> Result<(), io::Error> {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
println!("Is not a directory");
return Ok(());
}
for item in fs::read_dir(dir)? {
let file = match item {
Err(e) => {
println!("Error: {}", e);
return Ok(());
}
Ok(f) => f,
};
println!("");
}
println!("Done");
Ok(())
}
Before that
This is how you might transform your code to use ?
:
use std::{error::Error, fs, path::Path};
fn print_dir_contents() -> Result<String, Box<Error>> {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
return Err(Box::from("Is not a directory!"));
}
for entry in fs::read_dir(dir)? {
let path = entry?.path();
let file_name = path.file_name().unwrap();
println!("{}", file_name.to_string_lossy());
}
Ok("Done".into())
}
fn main() {
match print_dir_contents() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
There’s a lot of error handling here that you might not expect – other languages don’t tend to require it! But they exist in other languages – Rust just makes you know it. Here are the errors:
entry?
IO errors can happen during iteration.
path.file_name().unwrap()
Not all paths have file names. We can unwrap
this because read_dir
won’t give us a path without a file name.
file_name.to_string_lossy()
You can also to_str
and throw an error, but it’s nicer to do this. This error exists because not all file names are valid Unicode.
try!
and ?
throw errors into the return value, converting them to Box::Error
. It’s actually more reasonable to return an amalgamated error of all the things that can go wrong. Luckily io::Error
is just the right type:
use std::io;
// ...
fn print_dir_contents() -> Result<String, io::Error> {
// ...
if !dir.is_dir() {
return Err(io::Error::new(io::ErrorKind::Other, "Is not a directory!"));
}
// ...
}
Frankly, though, this check is already in fs::read_dir
, so you can actually just remove the if !dis.is_dir
altogether:
use std::{fs, io, path::Path};
fn print_dir_contents() -> Result<String, io::Error> {
let dir = Path::new("../FileSystem");
for entry in fs::read_dir(dir)? {
let path = entry?.path();
let file_name = path.file_name().unwrap();
println!("{}", file_name.to_string_lossy());
}
Ok("Done".into())
}
fn main() {
match print_dir_contents() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}