Error handling in Rust is done with values, not exceptions. Operations that can fail return Result<T, E>, and places where a value may be absent return Option<T>. In this article, we will review the key methods of these two types and summarize which handling to use in which situation. (The ? operator will be covered in detail in chapter 16.)
Learning Objectives
- Understand the meaning and difference between
Result<T, E>andOption<T>. - Besides
matchandif let, use chaining methods such asmap,and_then,unwrap_or. - Handle Result, which is frequently encountered in the standard library, like
parse::<i32>(). - Develop a sense of using
expect/unwraponly in safe places.
Key Concepts
1) Definitions of the Two Types
enum Option<T> { None, Some(T) }
enum Result<T, E> { Ok(T), Err(E) }
Option<T>— A place where a value may or may not exist.Result<T, E>— A place where the result can be success or failure. Use this when you need to express the reason for failure.
2) The Most Common Handling — match / if let
match parsed {
Ok(n) => println!("Success: {n}"),
Err(e) => println!("Failure: {e}"),
}
3) Option Method Table
| Method | Operation | Example |
|---|---|---|
map(|x| ...) |
Transform the value if Some | Some(2).map(|x| x*10) → Some(20) |
and_then(|x| ...) |
Return another Option if Some | Chaining |
unwrap_or(default) |
Default value when None | None.unwrap_or(-1) → -1 |
unwrap_or_else(|| ...) |
Calculated with a closure when None | |
ok_or(err) |
Option → Result | Some(1).ok_or("None") → Ok(1) |
is_some(), is_none() |
Presence check |
4) Result Method Table
| Method | Behavior |
|---|---|
map(|x| ...) |
Convert Ok value |
map_err(|e| ...) |
Convert Err value — useful for error type conversion |
ok() |
Result → Option (Err → None) |
err() |
Err to Option |
unwrap_or(default) |
Default value on Err |
expect(msg) |
Panic with message on Err |
? |
Propagate Err to caller (Part 16) |
5) When to use unwrap and expect?
Use only in places where failure is clearly impossible — or where failure leading to program termination is acceptable. For user input, file I/O, and network results,
?ormatchis the correct choice.
It is good to leave a message in expect("This spot is always Ok") explaining why it is safe.
Examples
Example 1 — ex01_parse.rs: Handling parse results
//Example 1 of Chapter 15: Handling `Result` from `str.parse`
//
//`parse::<i32>()` returns `Result<i32, ParseIntError>`.
//The caller must explicitly handle success or failure.
fn main() {
let inputs = ["42", "Error", " 7 ", "-3"];
for s in inputs {
//Trim whitespace before parsing
let parsed = s.trim().parse::<i32>();
match parsed {
Ok(n) => println!("'{s}' → {n}"),
Err(e) => println!("'{s}' → Failure: {e}"),
}
}
//unwrap_or: default value on failure
let n = "abc".parse::<i32>().unwrap_or(0);
println!("unwrap_or result = {n}");
}
Example 2 — ex02_option_methods.rs: Option methods
//Example 2 of Episode 15: Collection of Option Methods
//
// map / and_then / unwrap_or / unwrap_or_else / ok_or
fn half(n: i32) -> Option<i32> {
if n % 2 == 0 { Some(n / 2) } else { None }
}
fn main() {
let v = Some(10);
//map — Transform the value inside Some
let plus_one = v.map(|x| x + 1);
println!("map: {:?}", plus_one);
//and_then — Return another Option when Some (chaining)
let chained = v.and_then(half).and_then(half);
println!("and_then twice: {:?}", chained);
//unwrap_or — Default value when None
let backup = None::<i32>.unwrap_or(-1);
println!("unwrap_or(-1) = {backup}");
//ok_or — Convert Option to Result (add error message)
let r: Result<i32, &str> = Some(3).ok_or("No value");
println!("ok_or = {:?}", r);
}
Example 3 — ex03_result_methods.rs: Result methods
//Example 3 of Chapter 15: Collection of Result Methods
//
// map / map_err / ok / err / expect
fn main() {
let good: Result<i32, &str> = Ok(7);
let bad: Result<i32, &str> = Err("Input format error");
//map — Transform the value inside Ok (errors remain unchanged)
println!("good.map(|x| x*2) = {:?}", good.map(|x| x * 2));
println!("bad.map (|x| x*2) = {:?}", bad.map(|x| x * 2));
//map_err — Transform the value inside Err
let bad2: Result<i32, String> = bad.map_err(|e| format!("[Input error] {e}"));
println!("map_err: {:?}", bad2);
//ok — Result → Option (drop errors, convert to Some/None)
let opt = good.ok();
println!("good.ok() = {:?}", opt);
//expect — Panic on Err, print a message together (only when safety is obvious)
let safe = Ok::<i32, &str>(99).expect("This place is always Ok");
println!("expect result = {safe}");
}
Common Mistakes
Q. What is the difference between unwrap_or and unwrap_or_else?
A. unwrap_or(default) precomputes the default value — even for Some/Ok, the default expression is evaluated. If default computation is expensive, use unwrap_or_else(|| expensive_expression) so that it evaluates only when None/Err.
Q. Which is better, match or map?
A. If you are simply "converting the value inside Ok/Some," one line of map is clean. If each branch has different side effects (e.g., output), match is clearer.
Q. What is the difference between ? and unwrap?
A. ? propagates to the caller on Err/None, while unwrap immediately panics. Library code almost always uses ?. In small beginner examples, it's natural to quickly test with unwrap, then switch to ? for production code.
Q. What should be written in expect messages?
A. Writing "why an Err cannot occur at this point" helps the next person understand. Example: .expect("Input cannot be empty — verified above").
Summary
- Failure possibility is indicated in the type with
Result<T, E>, absence possibility withOption<T>. - Conversion/chaining uses methods like
map,and_then,map_err. - Recovery with default uses
unwrap_or/unwrap_or_else. unwrap/expectonly in safe and obvious places; otherwise, usematch/if let/?.
Try it yourself
Solve the assignment below. The reference solution is shown below.