Option in Rust: Some, None and handling missing values
How Rust's Option<T> replaces null: Some and None, match and if let, unwrap_or, map, and_then, the ? operator, and when unwrap is OK.
Rust has no null. A value that might be missing has the type Option<T>, an enum from the standard library:
enum Option<T> {
Some(T),
None,
}
An Option<u32> is either Some(5) or None, and the type system forces you to handle None before you can use the number. The famous "billion-dollar mistake" of null pointer crashes simply can't happen by accident.
Many standard methods return an Option: v.first(), v.get(i), v.pop(), map.get(&key), s.find('x'), iter.max(), s.chars().next(), text.parse().ok().
Getting the value out
fn find_price(item: &str) -> Option<u32> {
match item {
"apple" => Some(3),
"bread" => Some(5),
_ => None,
}
}
fn main() {
match find_price("apple") {
Some(p) => println!("apple costs {p}"),
None => println!("no apples"),
}
if let Some(p) = find_price("bread") {
println!("bread costs {p}");
}
println!("caviar costs {}", find_price("caviar").unwrap_or(0));
}
apple costs 3
bread costs 5
caviar costs 0
The methods worth knowing
| Method | What it does |
|---|---|
unwrap_or(default) |
the value, or a default |
unwrap_or_default() |
the value, or 0 / "" / empty |
unwrap_or_else(|| ...) |
the value, or compute a default (only when needed) |
map(|v| ...) |
transform the value if there is one |
and_then(|v| ...) |
chain another step that also returns an Option |
filter(|v| ...) |
turn Some into None if a test fails |
ok_or(err) |
turn it into a Result with an error for None |
is_some(), is_none() |
test without taking the value |
as_ref(), as_deref() |
borrow the contents: Option<&T>, Option<&str> |
copied(), cloned() |
Option<&T> to Option<T> |
take() |
move the value out, leaving None |
Inside a function that returns Option, the ? operator returns None early: let first = words.first()?;.
let else
To bail out when a value is missing and keep going with a plain value otherwise:
let Some(user) = find_user(id) else {
return Err("no such user".to_string());
};
// `user` is a plain value from here on
unwrap and expect
unwrap() returns the value or panics on None with "called Option::unwrap() on a None value". expect("message") panics with your message. They're fine in tests, quick scripts, and when None really is impossible (say why in the expect message). In code that handles user input or data from outside, handle None properly.
Common mistakes
- Using an
Optionas the value:let first: i32 = v.first();is E0308, expectedi32, foundOption<&i32>. - Option of a reference.
v.first()givesOption<&i32>, notOption<i32>; call.copied()to get the number. if x.is_some() { x.unwrap() }. Useif let Some(v) = xinstead: one check, no unwrap.- Using
Optionfor errors. If the caller needs to know why something failed, return aResult.
Example
#[derive(Debug)]
struct User {
name: String,
nickname: Option<String>,
}
fn find_user(users: &[User], name: &str) -> Option<usize> {
users.iter().position(|u| u.name == name)
}
fn display_name(u: &User) -> &str {
u.nickname.as_deref().unwrap_or(&u.name)
}
fn first_initial(users: &[User]) -> Option<char> {
let first = users.first()?;
first.name.chars().next()
}
fn main() {
let users = vec![
User { name: String::from("Ferris"), nickname: Some(String::from("crab")) },
User { name: String::from("Corro"), nickname: None },
];
for name in ["Corro", "Gopher"] {
match find_user(&users, name) {
Some(i) => println!("{name} is user #{i}, shown as {}", display_name(&users[i])),
None => println!("{name} not found"),
}
}
println!("{}", display_name(&users[0]));
println!("initial {:?}", first_initial(&users));
println!("no users: {:?}", first_initial(&[]));
let scores = vec![12, 40, 7];
let best = scores.iter().max().copied().unwrap_or(0);
let doubled = scores.get(1).map(|s| s * 2);
let big = scores.get(2).copied().filter(|s| *s > 10);
println!("best {best}, doubled {doubled:?}, big {big:?}");
let Some(last) = scores.last() else {
println!("no scores");
return;
};
println!("last {last}, {:?}", users[1]);
}
Output:
Corro is user #1, shown as Corro
Gopher not found
crab
initial Some('F')
no users: None
best 40, doubled Some(80), big None
last 7, User { name: "Corro", nickname: None }