» Quick Introduction to Rust » 2. Advanced » 2.6 Error Handling

Error Handling

Error handling is the process of handling the possibility of failure.

panic

panic prints an error message, starts unwinding the stack, and exits the program.

fn main() {
    let divisor = 0;
    if divisor == 0 {
        // If divisor is zero, panic with a custom error message
        panic!("Attempted to divide by zero!");
    }
    let result = 42 / divisor;
    println!("Result: {}", result);
}

Option

An enum called Option<T> in the std library is used when absence is a possibility. It manifests itself as one of two "options":

  • Some(T): An element of type T
  • None: No element

These two options can either be explicitly handled via match or implicitly with unwrap. Implicit handling will either return the inner element or panic.

fn main() {
    let some_value: Option<&str> = Some("literank");
    let unwrapped_value = some_value.unwrap();
    println!("Unwrapped value: {}", unwrapped_value);

    // Creating an Option with a None variant
    let none_value: Option<i32> = None;
    // Attempting to unwrap a None variant (will panic)
    let unwrapped_none = none_value.unwrap();
    println!("This line will not be reached due to the panic above");
}

Unpack with ?

You can unpack an Option by using match statements, but it's often easier to use the ? operator.

fn process_option_value(value: Option<i32>) -> Option<i32> {
    // Using the `?` operator to unwrap the Option and return None if it's None
    let unwrapped_value = value?;
    let result = unwrapped_value * 2;
    Some(result)
}

Result

Result is a richer version of the Option type that describes possible error instead of possible absence.

Result<T, E> could have one of two outcomes:

  • Ok(T): An element of type T
  • Err(E): An error with element E
#[derive(Debug)]
struct CustomError;

fn divide(a: i32, b: i32) -> Result<i32, CustomError> {
    if b == 0 {
        Err(CustomError)
    } else {
        Ok(a / b)
    }
}

Use Result in main

The Result type can also be the return type of the main function if specified explicitly.

fn main() -> Result<(), CustomError> {
    // Using ? to propagate errors in the main function
    let result = divide(58, 2)?;
    println!("Result is: {}", result);
    Ok(())
}

? is almost equivalent to an unwrap which return instead of panic on Err.

Iterate over Results

map operation might fail.

fn main() {
    let strings = vec!["literank", "58", "42"];
    let numbers: Vec<_> = strings
        .into_iter()
        .map(|s| s.parse::<i32>())
        .collect();
    println!("Results: {:?}", numbers);
    // Results: [Err(ParseIntError { kind: InvalidDigit }), Ok(58), Ok(42)]
}

You can ignore the failed items with filter_map.

fn main() {
    let strings = vec!["literank", "58", "42"];
    let numbers: Vec<_> = strings
        .into_iter()
        .filter_map(|s| s.parse::<i32>().ok())
        .collect();
    println!("Results: {:?}", numbers);
    // Results: [58, 42]
}

You can also collect the failed items with map_err.

fn main() {
    let strings = vec!["42", "literank", "58", "300", "18"];
    let mut errors = vec![];
    let numbers: Vec<_> = strings
        .into_iter()
        .map(|s| s.parse::<u8>())
        .filter_map(|r| r.map_err(|e| errors.push(e)).ok())
        .collect();
    println!("Numbers: {:?}", numbers);
    // Numbers: [42, 58, 18]
    println!("Errors: {:?}", errors);
    // Errors: [ParseIntError { kind: InvalidDigit }, ParseIntError { kind: PosOverflow }]
}

Or, you may choose partition to do it in a easier way.

fn main() {
    let strings = vec!["42", "literank", "58", "300", "18"];
    let (numbers, errors): (Vec<_>, Vec<_>) = strings
        .into_iter()
        .map(|s| s.parse::<i32>())
        .partition(Result::is_ok);
    println!("Numbers: {:?}", numbers);
    // Numbers: [Ok(42), Ok(58), Ok(300), Ok(18)]
    println!("Errors: {:?}", errors);
    // Errors: [Err(ParseIntError { kind: InvalidDigit })]
}

Result implements FromIterator so that a vector of results (Vec<Result<T, E>>) can be turned into a result with a vector (Result<Vec<T>, E>). Once an Result::Err is found, the iteration will terminate.

fn main() {
    let strings = vec!["literank", "58", "42"];
    let numbers: Result<Vec<_>, _> = strings
        .into_iter()
        .map(|s| s.parse::<i32>())
        .collect();
    println!("Results: {:?}", numbers);
    // Results: Err(ParseIntError { kind: InvalidDigit })
}

Code Challenge

Try to modify the code provided in the editor to handle errors in divide_numbers.

Loading...
> code result goes here
Prev
Next