← Back to Rust Course | Chapter 10: Error Handling | Lesson 5 of 6

Error Propagation Patterns

Error propagation is passing a problem up the chain to whoever is best equipped to decide what to do about it.

Propagating with ? Across Functions

A helper function can propagate an error with ?, letting its caller (or the caller's caller) ultimately decide how to react, rather than making that decision too early.

Example: Propagating with ? Across Functions

markup
fn parse_positive(s: &str) -> Result<i32, String> {
    let n: i32 = s.parse().map_err(|_| String::from("not a number"))?;
    if n < 0 {
        return Err(String::from("must be positive"));
    }
    Ok(n)
}

fn main() {
    println!("{:?}", parse_positive("5"));
    println!("{:?}", parse_positive("-3"));
}

Using Box<dyn Error> for Multiple Error Types

When a function can fail in multiple unrelated ways (e.g. parsing errors and custom validation errors), returning Box<dyn std::error::Error> lets it unify them under one flexible return type.

Example: Using Box<dyn Error> for Multiple Error Types

markup
use std::error::Error;

fn parse_and_check(s: &str) -> Result<i32, Box<dyn Error>> {
    let n: i32 = s.parse()?;
    Ok(n * 2)
}

fn main() -> Result<(), Box<dyn Error>> {
    let value = parse_and_check("21")?;
    println!("Value: {}", value);
    Ok(())
}

Adding Context While Propagating

A middle-layer function can catch an error, wrap it with more context using map_err, and then propagate the enriched error further up, giving callers a clearer picture of what happened.

Example: Adding Context While Propagating

markup
fn load_setting(raw: &str) -> Result<i32, String> {
    raw.parse::<i32>().map_err(|e| format!("failed to load setting: {}", e))
}

fn main() {
    match load_setting("abc") {
        Ok(v) => println!("Setting: {}", v),
        Err(e) => println!("{}", e),
    }
}

main Returning a Result

Rust's main function itself can return a Result, allowing the top-level program to use ? directly and print a clean error message automatically if something fails.

Example: main Returning a Result

markup
fn main() -> Result<(), std::num::ParseIntError> {
    let value: i32 = "99".parse()?;
    println!("Parsed: {}", value);
    Ok(())
}
Common Mistakes
  1. Handling every possible error deep inside a low-level helper function instead of letting it bubble up to a caller who has more context.
  2. Forgetting Box<dyn Error> is a common flexible return type when a function can produce several different unrelated error types.
  3. Overusing .unwrap() deep in a call chain instead of propagating the error upward with ?, which crashes the whole program instead of letting a caller decide.
Chapter Summary
  • Propagating errors upward with ? lets higher-level code decide how to handle or report failures.
  • Box<dyn std::error::Error> is a common return type for functions that may fail in more than one distinct way.
  • Layered functions can each add their own context before propagating an error further up the call stack.
  • Good error propagation keeps low-level functions simple while concentrating decision-making at higher levels.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.