Result, Option, and Errors
Idiomatic error handling in Rust web apps.
Learning Objectives
- Use Result in handlers
- Use ? operator
##No null, no exceptions
Rust has no null and no exceptions. Instead, two ordinary enums make absence and failure explicit in the type system: Option<T> and Result<T, E>. Once you are comfortable with them, error handling in Actix Web feels natural.
#Option: a value that may be missing
enum Option<T> {
Some(T),
None,
}A function that might not find something returns an Option:
fn find_user(id: u32) -> Option<String> {
if id == 1 { Some("Ada".to_string()) } else { None }
}
fn main() {
match find_user(1) {
Some(name) => println!("found {name}"),
None => println!("no such user"),
}
}The compiler forces you to handle None, so you cannot forget the missing case and crash with a null pointer.
#Result: an operation that may fail
enum Result<T, E> {
Ok(T),
Err(E),
}fn parse_age(text: &str) -> Result<u32, std::num::ParseIntError> {
text.parse::<u32>()
}#Handy methods
You rarely need a full match. These helpers cover most cases:
let name = find_user(2).unwrap_or("guest".to_string()); // default value
let len = find_user(1).map(|n| n.len()); // transform inside
let user = find_user(2).ok_or("not found")?; // Option -> Result
let age = parse_age("42").unwrap_or(0);maptransforms the inner value.unwrap_or/unwrap_or_elsegive a default.ok_orturns anOptioninto aResult.and_thenchains operations that may fail.
#The ? operator
Inside a function that returns Result, the ? operator returns the error early, or unwraps the success value:
fn double(text: &str) -> Result<u32, std::num::ParseIntError> {
let n = text.parse::<u32>()?; // returns Err immediately if parsing fails
Ok(n * 2)
}#Why this matters in Actix Web
Handlers can return Result<HttpResponse, MyError>, and ? converts failures into proper HTTP responses. Extractors use Option for optional query fields. Avoid unwrap() and expect() in request handling code: they panic, and a panic means a 500 error instead of a helpful message.
"What If?" Experiment
Learning by doing. Modify the code to see what happens.
Convert an Option into a Result.
Use find_user(2).ok_or("not found") and print the result.
You get Err("not found") instead of None.
Explanation: ok_or attaches an error value to None, which is how a missing record becomes a 404 in a handler.
Common Mistakes
Anticipate and avoid these frequent pitfalls when working with this concept.
Calling unwrap on user-controlled data
A missing or malformed value causes a panic and a 500 response.
Explanation: Convert the failure into your error type and return it. The client gets a clear 400 instead of a crash.
Using ? in a function that returns ()
? needs the function to return a Result or Option to carry the error.
Explanation: Change the return type to Result so the error has somewhere to go.
Practical Exercise
Parse safely with Result
TaskWrite parse_and_double that returns Err for invalid text and the doubled number otherwise, using ?.
Requirements
- Return Result<u32, std::num::ParseIntError>
- Use the ? operator
- Print the result for "21" and "abc"
Need a hint?
Try solving the exercise in your local environment first. Once you have a solution, or if you get completely stuck, reveal the answer.
Real-World Application
Every layer of a production service, from request parsing to database access, returns Result. The ? operator chains them together so a failure at any step becomes a single well-formed HTTP error.
Key Takeaways
- Option<T> is Some(value) or None; Result<T, E> is Ok(value) or Err(error).
- ? returns the error early and keeps the happy path clean.
- map, unwrap_or and ok_or cover most everyday needs.
- Do not unwrap in handlers; return a Result with a proper error instead.
Finished reading?
Mark this lesson as done to track your progress and earn XP.