Core Concepts

Error Handling

Handle errors gracefully with Rust's Result type, the ? operator, and custom error types.

Result in Rust

Rust has no exceptions. Errors are values returned as Result<T, E>:

  • Ok(value) — success
  • Err(error) — failure

The ? Operator

The ? operator propagates errors automatically. If Ok, unwraps the value. If Err, returns the error from the current function.

Custom Error Types

Define your own error types for rich error information. Implement the std::error::Error trait.

Libraries

  • thiserror: Easy custom error types
  • anyhow: Flexible error handling for applications

Example

rust
use std::fs::File;
use std::io::{self, Read};
use std::num::ParseIntError;

// Custom error type
#[derive(Debug)]
enum AppError {
    Io(io::Error),
    Parse(ParseIntError),
    Custom(String),
}

impl From<io::Error> for AppError {
    fn from(e: io::Error) -> Self { AppError::Io(e) }
}

impl From<ParseIntError> for AppError {
    fn from(e: ParseIntError) -> Self { AppError::Parse(e) }
}

// ? operator propagates errors
fn read_number_from_file(path: &str) -> Result<i32, AppError> {
    let mut file = File::open(path)?;  // ? converts io::Error to AppError
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let num: i32 = contents.trim().parse()?;  // ? converts ParseIntError
    Ok(num)
}

// Chaining with combinators
fn double_first(numbers: &[i32]) -> Result<i32, AppError> {
    let first = numbers.first()
        .ok_or_else(|| AppError::Custom("empty list".to_string()))?;
    Ok(first * 2)
}

fn main() {
    // match on Result
    match read_number_from_file("number.txt") {
        Ok(n) => println!("Got number: {}", n),
        Err(AppError::Io(e)) => println!("IO error: {}", e),
        Err(AppError::Parse(e)) => println!("Parse error: {}", e),
        Err(AppError::Custom(msg)) => println!("Error: {}", msg),
    }

    // unwrap_or_else for default
    let n = read_number_from_file("missing.txt").unwrap_or(0);
    println!("Number or default: {}", n);

    // map transforms Ok value
    let doubled = read_number_from_file("number.txt").map(|n| n * 2);

    // Result with vectors
    let strings = vec!["1", "2", "three", "4"];
    let numbers: Result<Vec<i32>, _> = strings.iter()
        .map(|s| s.parse::<i32>())
        .collect();
    println!("{:?}", numbers);  // Err(...)
}
Try it yourself — RUST