Rust doesn't have exceptions! Instead, it has the type Result<T, E> for
recoverable errors and the panic! macro that stops execution when the program
encounters an unrecoverable error.
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("hello.txt");
let mut username_file = match username_file_result {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match username_file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}
result
The Result type is an enum which has the variants Ok and Err.
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
unwrap
The unwrap_or_else method is useful for handling an error returned from a
function call:
let config = Config::build(&args).unwrap_or_else(|err| {
println!("Problem parsing arguments: {err}");
process::exit(1);
});
err or nothing
Sometimes a function will return an error or nothing (empty tuple) so we don't
want to call unwrap. In this case we can use if let:
if let Err(e) = run(config) {
println!("Application error: {e}");
process::exit(1);
}
is_ok
A helper method exists for quickly checking if there was no error:
let ignore_case = env::var("IGNORE_CASE").is_ok();
propogate
A lot of the time you'll want to send the error up to the caller:
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("hello.txt");
let mut username_file = match username_file_result {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match username_file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}
This pattern of propogating the error is so common that rust provides a helper
in the form of the ? operator:
fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("hello.txt")?;
let mut username = String::new();
username_file.read_to_string(&mut username)?;
return Ok(username);
}
The ? placed after a Result value is defined to work in almost the same way
as the match expressions in the first example. If the value of the Result is
an Ok, the value inside the Ok will get returned from this expression, and
the program will continue. If the value is an Err, the Err will be returned
from the while function as if we had used the return keyword.
There is one small difference though. The ? operator calls the from function
on any error values it processes to convert them to the error return type we've
given in our function signature. This is useful when a function returns one
error type to represent all the ways a function might fail, even if parts might
fail for many different reasons.
Rust programmers LOVE to chain shit together. So this is quite common:
fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?;
Ok(username)
}
We’re only allowed to use the ? operator in a function that returns Result,
Option, or another type that implements FromResidual.
main error return
You can actually change the return type of main slightly and Result<(), E> is
in fact valid. This means you can use it with the ? operator.
panic
Sometimes you need a mechanism to indicate an irrecoverable programming mistake
(rather than an input error). For this usecase rust has panic!() which unwinds
the stack, cleaning up all data along the way. This can be configured to instead
exit immediately (relying on the operating system's cleanup mechanisms).
Remember: a panic is always better than undefined behavior!
expect
For the case of return the okay value or panic on the error you can use the
expect() method:
let greeting_file = File::open("hello.txt")
.expect("hello.txt should be included in this project");