Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Recipe

Hand an error up with ?

Use when: a function calls something that returns a Result, and the function itself isn't the right place to decide what to do about a failure

Goal: a function that returns Result and passes every failure to its caller

Taught in The question mark operator

  1. 1

    Change the return type from T to Result<T, E>, where E is the error type of the calls inside.

    Example

    fn count_lines(path: &str) -> usize becomes fn count_lines(path: &str) -> Result<usize, io::Error>, because read_to_string fails with an io::Error.

    Why: ? returns early with an Err, and a function can only return an Err if its return type is a Result.

  2. 2

    Replace each match that returns Err(e) early, or each unwrap, with a ? right after the call.

    Example

    fs::read_to_string(path).unwrap() becomes fs::read_to_string(path)?.

    Why: each ? either gives the success value to the rest of the line or returns the error to the caller.

  3. 3

    Make the last expression Ok(...) around the answer.

    Why: the function now returns a Result, so a successful answer must be an Ok.

  4. 4

    Every caller now receives a Result. Each one either handles it (a match, a fallback) or, if it isn't the right place either, repeats these steps.

    Why: the error has to end up somewhere that can respond to it, usually main or close to it.

Common mistakes

  • Adding ? without changing the return type (E0277).
  • Forgetting the Ok(...) around the final value (E0308, mismatched types).
  • Putting ? on a call whose error type differs from the function's, with no conversion between them (E0277, couldn't convert the error).