Step 4 of 7
Option: a value that might be missing
Many languages have null: a special "nothing here" value that almost any variable can hold. Forget to check for it, and the program crashes (Java's NullPointerException, C's segmentation fault) or quietly misbehaves. Tony Hoare, who invented null references in 1965, later called them his "billion-dollar mistake".
Rust has no null. A String is always a real string; a u32 is always a number. When a value might be missing, the type says so: Option<T>, an ordinary enum from the standard library:
enum Option<T> {
None,
Some(T),
}
Tis a type parameter, a placeholder for any type:Option<u32>is "maybe a u32",Option<String>is "maybe a String". (The generics module shows how to write your own types like this.)Optionand its variants are in the prelude, the set of names every Rust file can use without importing, so you writeSome(5)andNone, notOption::Some(5).- You've met it already:
checked_addfrom the types module returnsSome(sum), orNonewhen the sum would overflow.
Returning an Option
The classic use is a lookup that may find nothing:
struct Item {
name: String,
price_cents: u32,
}
fn find_price(items: &[Item], name: &str) -> Option<u32> {
for item in items {
if item.name == name {
return Some(item.price_cents);
}
}
None
}
fn main() {
let menu = [
Item { name: String::from("tea"), price_cents: 300 },
Item { name: String::from("cake"), price_cents: 450 },
];
for wanted in ["cake", "pizza"] {
match find_price(&menu, wanted) {
Some(price) => println!("{}: {} cents", wanted, price),
None => println!("{}: not on the menu", wanted),
}
}
println!("{:?} {:?}", find_price(&menu, "tea"), find_price(&menu, "soup"));
}
cake: 450 cents
pizza: not on the menu
Some(300) None
The compiler makes you handle None
An Option<u32> is not a u32, so you can't use it like one:
fn main() {
let price: Option<u32> = Some(250);
let total = price + 50;
println!("{}", total);
}
error[E0369]: cannot add `{integer}` to `Option<u32>`
That's the whole point. To get at the number you must first say what happens when it's missing, and you can't forget, because the code doesn't compile until you do. With null, the "missing" case is invisible in the type and the check is up to your memory.
Getting the value out
match, as above. It always works and handles both cases explicitly..unwrap()gives the value insideSome, and panics onNone..expect("message")does the same with your message (you've used it onparse). Fine in tests, quick scripts, and whenNonewould be a bug in your own code; in normal program logic, handle theNone..unwrap_or(default)gives the value, ordefaultforNone:find_price(&menu, "soup").unwrap_or(0)..is_some()and.is_none()answer the yes/no question without taking the value out.
The errors module adds many more helpers (map, and_then, ?). Here's what an unlucky unwrap does:
fn main() {
let empty: Option<u32> = None;
println!("before");
let n = empty.unwrap();
println!("{}", n);
}
before
The program stops with called `Option::unwrap()` on a `None` value. An unwrap you didn't think through is the Rust version of a null pointer crash, so reach for match (and the next step's if let and let...else) first.
A workplace note: Option costs nothing extra in the common cases. Option<&T> is the same size as &T: the compiler uses the null address internally to mean None, and your code can never see or misuse it.
Common mistakes
- Printing an Option with
{}:error[E0277]: `Option<u32>` doesn't implement `std::fmt::Display`. Use{:?}(which printsSome(300)orNone), or take the value out first. - Returning the value instead of wrapping it: in a function returning
Option<u32>, writereturn Some(price);, notreturn price;(mismatched types).
Your task
Write fn find_user(users: &[User], id: u32) -> Option<&User> that returns a reference to the user with that id, or None if there isn't one. Returning &User lends the caller the user from the slice, with no copying.
Challenge 2
Write fn safe_divide(a: i32, b: i32) -> Option<i32>: None when b is 0 (dividing by zero would panic), otherwise Some(a / b).
Challenge 3
Write fn cheapest(items: &[Item]) -> Option<&Item> from scratch: the item with the lowest price_cents. If several share the lowest price, return the first of them. An empty slice has no cheapest item.
Previous: Variants that carry data Next: if let and let...else