Skip to main content
eLearner.app
Module 7 · Lesson 2 of 214/14 in the course~15 min
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:

Code
enum Result<T, E> {
    Ok(T),
    Err(E),
}

We can use pattern matching to inspect the result:

Code
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(...).

Code
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 (typically Result, Option, or types implementing FromResidual).


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:

Code
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

Exercise#rust.m7.l2.e1
Attempts: 0Loading…

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.

Loading editor…
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

Exercise#rust.m7.l2.e2
Attempts: 0Loading…

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.

Loading editor…
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

Exercise#rust.m7.l2.e3
Attempts: 0Loading…

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.

Loading editor…
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