Error Handling (the ? operator)
Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and it handles recoverable errors with Result rather than exceptions, made ergonomic by the ? operator.
Learn Error Handling (the ? operator) in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll see how the ? operator replaces verbose match boilerplate to propagate errors cleanly up the call stack.
What You'll Learn in This Lesson
1️⃣ The Verbose Way: Propagating with match
Many operations return a Result . To pass an error up to your caller, you can match on it and return Err(e) on failure. This works, but it's noisy — especially when several steps can each fail.
Every fallible step needs its own four-line match . Imagine doing this five times in one function — the real logic gets buried. Rust has a much cleaner tool.
2️⃣ The ? Operator
The ? operator does exactly what that match did, in one character. On an Ok it unwraps the value; on an Err it returns that error from the function immediately. The only requirement is that the enclosing function returns a Result (or Option ).
In sum_two , if the first parse fails the function returns right there and never touches the second — short-circuiting on the first error. That's the power of ? : clean, linear code that still handles every failure.
Your turn. Fill in the blanks marked ___ , then run it.
Mini-Challenge: A Safe Average
Parse a slice of strings to numbers with ? and compute their average, returning a Result . Run it with cargo run .
Common Errors (and the fix)
- "the operator can only be used in a function that returns " — Change the function's return type to a Result (or Option ).
- Forgetting to wrap the success — After your last step, return Ok(value) , not a bare value.
- " couldn't convert the error" — The error types differ and no From conversion exists. Use a common error type like Box<dyn Error> .
- Using ? on a non-Result — ? only works on Result and Option values.
Pro Tips
- 💡 Make main return a Result: fn main() -> Result<(), Box<dyn std::error::Error>> lets you use ? at the top level.
- 💡 Box errors to mix types: Box<dyn Error> lets ? combine different error kinds easily.
- 💡 Reserve unwrap for prototypes: in real code, prefer ? or match so failures don't panic.
📋 Quick Reference — Error Handling
Goal
Rust Syntax
Propagate error
let v = expr?;
Return success
Ok(value)
Return failure
Err(e)
Handle locally
match expr { Ok(v) => .., Err(e) => .. }
Flexible error type
Box<dyn Error>
Frequently Asked Questions
🎉 Lesson Complete!
- ✅ Rust handles recoverable errors with Result , not exceptions
- ✅ expr? unwraps Ok or returns Err early
- ✅ ? requires a Result -returning function and converts errors via From
- ✅ Use ? to propagate, match to handle, unwrap sparingly
- ✅ Next lesson: Modules & Crates — organising code and using libraries
Practice quiz
How does Rust handle recoverable errors?
- With exceptions
- With null returns
- With the Result type
- By aborting
Answer: With the Result type. Rust uses Result<T, E> for recoverable errors rather than exceptions.
On an Ok value, what does the ? operator do?
- Unwraps the value and continues
- Returns early
- Panics
- Logs the value
Answer: Unwraps the value and continues. On Ok(value), ? evaluates to value and execution continues.
On an Err, what does the ? operator do?
- Ignores it
- Retries the operation
- Converts it to Ok
- Immediately returns the Err from the enclosing function
Answer: Immediately returns the Err from the enclosing function. On Err(e), ? returns Err(e) early from the current function.
Why must a function using ? return a Result (or Option)?
- For speed
- Because ? returns the error out of the function, which needs a Result return type to hold it
- Because ? prints errors
- It doesn't have to
Answer: Because ? returns the error out of the function, which needs a Result return type to hold it. ? short-circuits by returning the error, so there must be somewhere (a Result) for that Err to go.
What does ? replace compared to manual error handling?
- A verbose match that returns Err(e) early
- A loop
- A panic
- An if statement
Answer: A verbose match that returns Err(e) early. ? collapses a four-line match-and-return-early into a single character.
In multiply(a, b) using ?, what does multiply("6", "7") return?
- 42
- Err(..)
- Ok(42)
- 13
Answer: Ok(42). Both parse successfully, so the function returns Ok(42) (6 * 7 wrapped in Ok).
In sum_two where the first parse fails, what happens to the second parse?
- It still runs
- It is never reached — ? short-circuits on the first error
- It panics
- It returns Ok
Answer: It is never reached — ? short-circuits on the first error. ? returns on the first error, so execution never touches the second parse.
Can ? convert between different error types?
- No
- Only for strings
- Only inside main
- Yes, it calls .into() via the From trait when a conversion exists
Answer: Yes, it calls .into() via the From trait when a conversion exists. When the error type differs, ? auto-converts using From, which is why Box<dyn Error> works so smoothly.
Which is the idiomatic choice to propagate an error to the caller?
- unwrap
- the ? operator
- panic!
- expect
Answer: the ? operator. Use ? to propagate; use match/if let to handle locally; reserve unwrap/expect for impossible cases or prototypes.
For average(values) that parses each &str with ?, what does average(&["10", "oops"]) return?
- Ok(5.0)
- Ok(20.0)
- Err(ParseIntError ..)
- 0.0
Answer: Err(ParseIntError ..). "oops" fails to parse, so ? returns the Err immediately and the function yields Err(ParseIntError).
Continue this course
- Previous: HashMaps
- Next: Modules & Crates