Module lessons (2/2)
Error Handling and the ? Operator
Rust's philosophy encourages you to recognize the possibility of errors explicitly and structure your code to handle them before the program is compiled.
Errors in Rust are divided into two main categories: unrecoverable errors (which cause immediate termination via the panic! macro) and recoverable errors (managed using the Result<T, E> type).
The Result<T, E> Type and Pattern Matching
Most recoverable errors return a Result<T, E> type, which is an enum defined as:
enum Result<T, E> {
Ok(T),
Err(E),
}
We can use pattern matching to inspect the result:
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
Error Propagation with the ? Operator
When implementing a function, instead of handling the error directly inside it, we often want to return the error to the caller so they can decide what to do. This process is called error propagation.
Rust provides the ? operator as a syntactic shortcut to propagate errors. If the value of a Result is Ok, the inner value of Ok is returned by the expression; if it is Err, the Err error is returned from the entire current function as if we had used a return Err(...).
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("username.txt")?;
let mut username = String::new();
username_file.read_to_string(&mut username)?;
Ok(username)
}
[!IMPORTANT] The
?operator can only be used in functions that return a type compatible with the value it is applied to (typicallyResult,Option, or types implementingFromResidual).
Custom Errors
We can define our own custom error types by implementing the std::fmt::Display trait (and optionally std::error::Error) to provide a human-readable representation of the error:
use std::fmt;
#[derive(Debug)]
struct MyError {
details: String,
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error: {}", self.details)
}
}
impl std::error::Error for MyError {}
Try it yourself
Exercise 1: Reading a file with the ? operator
Complete the read_username_from_file function so that it uses the ? operator to propagate errors generated by opening the file and reading its content into the username string.
Show hint
Add the `?`character after`File::open(...)`and after`username_file.read_to_string(...)` to propagate errors.
Solution available after 3 attempts
Exercise 2: Parse and Double a Number
Write a function named parse_and_double that accepts a string reference &str and returns a Result<i32, std::num::ParseIntError>. The function should parse the string using val.parse::<i32>() with ? to propagate parsing errors, and on success return the doubled value wrapped in Ok.
Show hint
Use `val.parse::<i32>()?`to parse the string and propagate the error, then return`Ok(num \* 2)`.
Solution available after 3 attempts
Exercise 3: Defining a Custom Error
Define an empty structure named CustomError. Implement the std::fmt::Display trait for it by writing 'Si e verificato un errore personalizzato' as the error message in the fmt method.
Show hint
Declare `struct CustomError;`and implement Display by writing`write!(f, "Si e verificato un errore personalizzato")`inside the`fmt` method.
Solution available after 3 attempts