Rust Error Handling

Safe and explicit error management in Rust

⚠️ What is Rust Error Handling?

Rust handles errors explicitly using Result and Option types instead of exceptions. This approach makes errors visible in function signatures, forcing developers to handle them properly and preventing crashes.


// Function that might fail
fn divide(a: f64, b: f64) -> Result {
    if b == 0.0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(a / b)
    }
}
                                    

Error Handling Types

Result<T, E>

For operations that can fail

fn parse_number(s: &str) -> Result {
    s.parse()
}

Option<T>

For values that might not exist

fn find_user(id: u32) -> Option {
    if id == 1 { Some("Alice".to_string()) } 
    else { None }
}
💥

panic!

For unrecoverable errors

fn critical_error() {
    panic!("Something went very wrong!");
}
🔧

Custom Errors

Your own error types

#[derive(Debug)]
enum MyError {
    NotFound,
    InvalidInput,
}

🔹 Working with Result

Result is used for operations that can succeed or fail:

use std::fs::File;
use std::io::Read;

fn read_file_content(filename: &str) -> Result {
    let mut file = File::open(filename)?;  // ? operator for error propagation
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    Ok(content)
}

fn main() {
    match read_file_content("example.txt") {
        Ok(content) => println!("File content: {}", content),
        Err(error) => println!("Error reading file: {}", error),
    }
}

Output (if file exists):

File content: Hello, world!

🔹 Working with Option

Option represents values that might not exist:

fn find_word(text: &str, word: &str) -> Option {
    text.find(word)
}

fn main() {
    let text = "Hello, Rust programming!";
    
    // Using match
    match find_word(text, "Rust") {
        Some(index) => println!("Found 'Rust' at index: {}", index),
        None => println!("'Rust' not found"),
    }
    
    // Using if let
    if let Some(index) = find_word(text, "Python") {
        println!("Found 'Python' at index: {}", index);
    } else {
        println!("'Python' not found");
    }
}

Output:

Found 'Rust' at index: 7

'Python' not found

🔹 The ? Operator

Simplify error propagation with the ? operator:

🔸 Without ? operator

fn parse_and_double(s: &str) -> Result {
    match s.parse::() {
        Ok(n) => Ok(n * 2),
        Err(e) => Err(e),
    }
}

🔸 With ? operator

fn parse_and_double(s: &str) -> Result {
    let n = s.parse::()?;  // Automatically returns error if parsing fails
    Ok(n * 2)
}

fn main() {
    match parse_and_double("21") {
        Ok(result) => println!("Result: {}", result),
        Err(e) => println!("Error: {}", e),
    }
}

Output:

Result: 42

🔹 Custom Error Types

Create your own error types for better error handling:

#[derive(Debug)]
enum CalculatorError {
    DivisionByZero,
    InvalidOperation,
    Overflow,
}

impl std::fmt::Display for CalculatorError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            CalculatorError::DivisionByZero => write!(f, "Cannot divide by zero"),
            CalculatorError::InvalidOperation => write!(f, "Invalid operation"),
            CalculatorError::Overflow => write!(f, "Number overflow"),
        }
    }
}

impl std::error::Error for CalculatorError {}

fn safe_divide(a: f64, b: f64) -> Result {
    if b == 0.0 {
        Err(CalculatorError::DivisionByZero)
    } else {
        Ok(a / b)
    }
}

fn main() {
    match safe_divide(10.0, 0.0) {
        Ok(result) => println!("Result: {}", result),
        Err(e) => println!("Error: {}", e),
    }
}

Output:

Error: Cannot divide by zero

🔹 Useful Methods

Common methods for working with Result and Option:

fn main() {
    let result: Result = Ok(42);
    let option: Option = Some(10);
    
    // unwrap() - panics if error/None
    let value = result.unwrap();
    println!("Unwrapped: {}", value);
    
    // unwrap_or() - provides default value
    let default_value = option.unwrap_or(0);
    println!("With default: {}", default_value);
    
    // map() - transform the success value
    let doubled = result.map(|x| x * 2);
    println!("Doubled: {:?}", doubled);
    
    // and_then() - chain operations
    let chained = result.and_then(|x| Ok(x + 10));
    println!("Chained: {:?}", chained);
}

Output:

Unwrapped: 42

With default: 10

Doubled: Ok(84)

Chained: Ok(52)

🧠 Test Your Knowledge

What does the ? operator do in Rust?