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.
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
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
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
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)