Lesson 5 of 12
Error Handling
Learn how Rust handles with Result<T, E> and missing values with Option<T>. Because these are ordinary types, the makes sure you deal with every failure case.
use std::num::ParseIntError;
// Result<T, E> — either Ok(value) or Err(error)
fn parse_number(s: &str) -> Result<i32, ParseIntError> {
s.trim().parse::<i32>()
}
fn main() {
// Pattern matching on Result
match parse_number("42") {
Ok(n) => println!("Parsed: {}", n),
Err(e) => println!("Error: {}", e),
}
// ? operator — propagate errors upward
// (only works in functions that return Result or Option)
let result: Result<i32, _> = (|| {
let a = parse_number("10")?;
let b = parse_number("20")?;
Ok(a + b)
})();
println!("{:?}", result); // Ok(30)
// unwrap() — panics on Err (fine for scripts, not for libraries)
let n = parse_number("42").unwrap(); // 42
// unwrap_or — provide a default
let n = parse_number("oops").unwrap_or(0); // 0
// unwrap_or_else — compute default lazily
let n = parse_number("oops").unwrap_or_else(|e| {
eprintln!("Falling back: {}", e);
-1
});
}// Option<T> — either Some(value) or None
fn first_even(numbers: &[i32]) -> Option<i32> {
numbers.iter().find(|&&x| x % 2 == 0).copied()
}
fn main() {
let odds = vec![1, 3, 5, 7];
let mixed = vec![1, 2, 3, 4];
println!("{:?}", first_even(&odds)); // None
println!("{:?}", first_even(&mixed)); // Some(2)
// if let — concise match for one variant
if let Some(n) = first_even(&mixed) {
println!("Found even: {}", n);
}
// Option methods
let opt: Option<i32> = Some(5);
println!("{}", opt.unwrap_or(0)); // 5
println!("{:?}", opt.map(|x| x * 2)); // Some(10)
println!("{:?}", opt.filter(|&x| x > 3)); // Some(5)
let none: Option<i32> = None;
println!("{}", none.unwrap_or(42)); // 42
println!("{:?}", none.map(|x| x * 2)); // None
}Common question
Does Rust's lack of exceptions make error handling harder?
Rust puts failure into the type system. Result<T, E> is either a value or an error, and Option<T> is either a value or nothing. Because they are ordinary enums, the compiler makes you deal with both cases, and pattern matching or the ? operator keeps that short.
In practice: return Result from anything that can fail for outside reasons, pass errors up with ?, convert error types with From, and save panic for situations that mean your own code is broken. Libraries define their own error enums; applications often use a crate such as anyhow to carry any error along with context.
Source: Result is marked #[must_use], so the compiler warns if you ignore one. An error has to be handled or deliberately passed on.
Common mix-ups
Each of these sounds reasonable. Think about why it is wrong, then open the explanation.
Without exceptions, every call has to be checked by hand, which clutters the code.
unwrap is a reasonable default, since most operations succeed.
Option and Result are two ways of writing the same thing.
Tip: use the ← and → keys to move between lessons.