← 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.
Syntax
rust
fn name() -> Result<T, Box<dyn Error>> {
    let value = step()?;
    Ok(value)
}

impl From<Source> for NameError {
    fn from(e: Source) -> Self { NameError::Variant(e) }
}

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
// Define the function `parse_positive` taking `s` and returning a `Result<i32, String>`
fn parse_positive(s: &str) -> Result<i32, String> {
    // Declare an immutable variable `n` (an integer), set to `s.parse().map_err(|_| String::from("not a number"))?`
    let n: i32 = s.parse().map_err(|_| String::from("not a number"))?;
    // Check whether `n < 0`
    if n < 0 {
        // Return `Err(String::from("must be positive"))` from this function
        return Err(String::from("must be positive"));
    }
    Ok(n)
}

// Entry point: execution of the program starts here
fn main() {
    // Print "{:?}" to the console, substituting in `parse_positive("5")` (with a trailing newline)
    println!("{:?}", parse_positive("5"));
    // Print "{:?}" to the console, substituting in `parse_positive("-3")` (with a trailing newline)
    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
// Bring `std::error::Error` into scope
use std::error::Error;

// Define the function `parse_and_check` taking `s` and returning a `Result<i32, Box<dyn Error>>`
fn parse_and_check(s: &str) -> Result<i32, Box<dyn Error>> {
    // Declare an immutable variable `n` (an integer), set to `s.parse()?`
    let n: i32 = s.parse()?;
    Ok(n * 2)
}

// Define the function `main` with no parameters and returning a `Result<(), Box<dyn Error>>`
fn main() -> Result<(), Box<dyn Error>> {
    // Declare an immutable variable `value`, set to `parse_and_check("21")?`
    let value = parse_and_check("21")?;
    // Print "Value: {}" to the console, substituting in `value` (with a trailing newline)
    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
// Define the function `load_setting` taking `raw` and returning a `Result<i32, String>`
fn load_setting(raw: &str) -> Result<i32, String> {
    raw.parse::<i32>().map_err(|e| format!("failed to load setting: {}", e))
}

// Entry point: execution of the program starts here
fn main() {
    // Match on `load_setting("abc")` and branch by pattern
    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
// Define the function `main` with no parameters and returning a `Result<(), std::num::ParseIntError>`
fn main() -> Result<(), std::num::ParseIntError> {
    // Declare an immutable variable `value` (an integer), set to `"99".parse()?`
    let value: i32 = "99".parse()?;
    // Print "Parsed: {}" to the console, substituting in `value` (with a trailing newline)
    println!("Parsed: {}", value);
    Ok(())
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
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.