Rust: The Option Type

Home · Blog

4 February 2023

The Option type in the Rust standard library is pretty simple:

enum Option<T> {
    None,
    Some(T),
}

An Option<T> is either no value (None) or a value of type T (Some(T)). This little type does a lot of work, though: it replaces null pointers; it can take the place of default values; it’s used for optional fields, arguments, and results; and it can be used for error handling in simple cases. Like other enums, we can use pattern matching to get at the value:

fn main() {
    let some_number = Some(4);
    let no_number: Option<i32> = None;
    print_optional_number(some_number);   // prints: got 4
    print_optional_number(no_number);     // prints: no number
}

fn print_optional_number(number: Option) {
    match number {
        Some(n) => println!("got {}", n),
        None => println!("no number"),
    }
}

In practice, it’s more common to use one of Option’s methods. I’ll introduce some of the more common ones here. Refer to std::option for the whole list.

The basics

If you just want to find out if an Option value is Some or None, is_some and is_none do what you’d expect:

println!("{}", some_number.is_some());   // true
println!("{}", no_number.is_none());     // true

If you end up with an Option<Option<T>>, you can remove one layer of Option with flatten:

let nested = Some(Some(4));
println!("{:?}", nested.flatten());   // Some(4)

If T implements PartialOrd, then so does Option<T>:

println!("{}", no_number != some_number);   // true
println!("{}", no_number < some_number);    // true

Getting the value

If you’re sure your Option<T> isn’t None and you just want to get the T value out, you can use unwrap and risk a panic if you were wrong:

println!("{}", some_number.unwrap());   // 4
println!("{}", no_number.unwrap());     // panics

The expect method does the same thing, but it lets you customize the panic message:

println!("{}", some_number.expect("oops!"));   // 4
println!("{}", no_number.expect("oops!"));     // panics with "oops!"

Finally, the unwrap_or methods let you supply a default for the None case:

println!("{}", no_number.unwrap_or(5));          // 5
println!("{}", no_number.unwrap_or_default());   // 0, the default for i32
fn f() -> i32 { 6 }
println!("{}", no_number.unwrap_or_else(f));     // 6

Applying a function to the value

Sometimes you want to transform the value in the Some case, and leave the None case alone. The map method does that:

println!("{:?}", some_number.map(|n| n * 2));   // Some(8)
println!("{:?}", no_number.map(|n| n * 2));     // None

The map_or method lets you supply a function for the Some case and a default value for the None case, combining the functionality of the map and unwrap_or methods:

println!("{:?}", some_number.map_or(0, |n| n * 2));   // 8
println!("{:?}", no_number.map_or(0, |n| n * 2));     // 0

The filter method does something a little different: if the supplied function returns false, it turns a Some value into a None value.

println!("{:?}", some_number.filter(|&n| n < 5));   // Some(4)
println!("{:?}", some_number.filter(|&n| n < 4));   // None
println!("{:?}", no_number.filter(|&n| n < 4));     // None

Pipelines

Sometimes you have several functions that return Option values and that you want to combine. The and_then and or_else functions help you stitch them together without any match statements:

println!("{:?}", some_number.and_then(|n| Some(n * 2)));   // Some(8)
println!("{:?}", no_number.and_then(|n| Some(n * 2)));     // None
println!("{:?}", some_number.or_else(|| Some(0)));   // Some(4)
println!("{:?}", no_number.or_else(|| Some(0)));     // Some(0)