RustArena

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),
}

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

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

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