RustArena

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

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 }

Practice it