Option in Depth
Prerequisites: Cargo, dependencies & workspaces
Level 1 introduced Option<T>: a value that is either Some(value) or None.
Matching on it every time gets repetitive, so Option has methods for the
common cases. Most of them take a closure — a small inline function
written |x| x + 1. Closures get their own lesson later; for now read
|x| expr as "given x, produce expr".
Syntax
fn main() {
let some: Option<i32> = Some(4);
let none: Option<i32> = None;
let _ = some.map(|n| n * 2); // Some(8): transform the inner value
let _ = some.and_then(|n| n.checked_div(0)); // None: the closure itself returns an Option
let _ = some.filter(|n| *n > 3); // Some(4): keep only if the test passes
let _ = none.unwrap_or(0); // 0: fallback value
let _ = none.unwrap_or_else(|| 2 + 2); // 4: fallback computed only when needed
let _ = none.unwrap_or_default(); // 0: the type's Default value
let _ = none.ok_or("missing"); // Err("missing"): convert to a Result
let _ = some.is_some(); // true
}
map is for closures that return a plain value; and_then is for closures
that return another Option, and flattens the result so you don't end up with
Option<Option<T>>.
The ? operator on Option
Inside a function that returns Option, writing ? after an Option means
"if this is None, return None from the whole function right now;
otherwise give me the inner value":
fn first_even_squared(numbers: &[i32]) -> Option<i32> {
let first = numbers.first()?; // returns None early for an empty slice
if first % 2 != 0 {
return None;
}
Some(first * first)
}
Example
struct User {
name: String,
nickname: Option<String>,
}
fn parse_age(input: &str) -> Option<u32> {
input.trim().parse().ok() // .ok() turns a Result into an Option
}
fn nickname_initial(user: &User) -> Option<char> {
let nick = user.nickname.as_ref()?; // as_ref: borrow the String instead of moving it
nick.chars().next()
}
fn main() {
println!("{:?}", parse_age(" 41 ").map(|age| age + 1));
println!("{}", parse_age("forty").unwrap_or(0));
let even_doubled = Some(8).filter(|n| n % 2 == 0).map(|n| n * 2);
println!("{even_doubled:?}");
let ada = User { name: String::from("Ada"), nickname: Some(String::from("countess")) };
let bob = User { name: String::from("Bob"), nickname: None };
for user in [&ada, &bob] {
let initial = nickname_initial(user)
.map(|c| c.to_ascii_uppercase())
.unwrap_or('-');
println!("{}: {}", user.name, initial);
}
let checked: Result<u32, String> = parse_age("x").ok_or(String::from("not a number"));
println!("{checked:?}");
let label = bob.nickname.as_deref().unwrap_or("(none)");
println!("Bob's nickname: {label}");
}
Expected output
Some(42)
0
Some(16)
Ada: C
Bob: -
Err("not a number")
Bob's nickname: (none)
as_deref() turns an &Option<String> into an Option<&str>, which is handy
for supplying a &str default without allocating a new String.
Common mistakes
- Calling
.unwrap()on aNone. It compiles but panics at runtime withcalled `Option::unwrap()` on a `None` value. Useunwrap_or,?, or amatchunlessNoneis truly impossible. - Using
?in a function that returns(), such asmain. The compiler saysthe `?` operator can only be used in a function that returns `Result` or `Option`— change the return type or handle theNoneexplicitly. - Writing
opt.unwrap_or(expensive()). The argument is evaluated even whenoptisSome. Useunwrap_or_else(|| expensive())so the work only happens when it is needed. - Using
mapwith a closure that returns anOptionand gettingOption<Option<T>>. Useand_theninstead. - Moving a
Stringout of a struct withuser.nickname?when you only have&User:error[E0507]: cannot move out of `user.nickname` which is behind a shared reference. Borrow it with.as_ref()first.
Practice
Write fn middle_char(word: &str) -> Option<char> that returns the middle
character of a word with an odd number of characters and None otherwise
(including for an empty string). Use it to print Some('s') for "rusty"
and None for "rust".
Show solution
fn middle_char(word: &str) -> Option<char> {
let len = word.chars().count();
if len % 2 == 0 {
return None;
}
word.chars().nth(len / 2)
}
fn main() {
println!("{:?}", middle_char("rusty"));
println!("{:?}", middle_char("rust"));
}