Optional Values
Store Optional Values in Option
Rust has no null. Instead, it represents the presence or absence of a value with the std::option::Option↗ enum:
enum Option<T> {
None,
Some(T),
}
Every std::option::Option↗ is either std::option::Option::Some↗ and contains a value, or std::option::Option::None↗, and does not.
fn main() { // `Some` is a variant of the `Option` enum that represents a value. let some_number = Some(5); println!("{some_number:?}"); // `None` is another variant of the `Option` enum // that represents the _absence_ of a value. let absent_number: Option<i32> = None; println!("{absent_number:?}"); }
Option<T> is similar to the concept of "nullable" types in other languages, but with a crucial difference: the Option type forces you to explicitly handle both the "value present" and "value absent" cases, preventing common null pointer errors.
Use Option with match, if let, or while let
Option<T>↗ is often used with match↗, if let↗, or while let↗:
/// This function simulates baking a cake. /// /// It takes an optional `sprinkles` argument, which determines /// whether or not to add sprinkles to the cake. /// /// Returns a string describing the cake. fn bake_cake(sprinkles: Option<&str>) -> String { let mut cake = String::from("A delicious cake..."); // Add required ingredients. // Handle optional sprinkles. // `if let` binds the `Option` to a `Some(...)` pattern. // If and only if there is a value, `sprinkle_choice` is assigned the // content of the option. if let Some(sprinkle_choice) = sprinkles { cake.push_str( // `sprinkle_choice` is never "null" / unassigned. format!(" with a sprinkle of {sprinkle_choice}").as_str(), ); } else { // Optional fallback behavior if `sprinkles` is `None`. cake.push_str(" ready for your decorating touch!"); } cake } fn main() { println!("{}", bake_cake(Some("rainbow nonpareils"))); // The absence of a value is noted explicitly with `None`. println!("{}", bake_cake(None)); }
fn divide(numerator: f64, denominator: f64) -> Option<f64> { if denominator == 0.0 { None } else { Some(numerator / denominator) } } fn main() { let result1 = divide(10.0, 2.0); match result1 { // Handles the `Some` case. Some(value) => println!("Result: {value}"), // Handles the `None` case. None => println!("Cannot divide by zero!"), } }
Implement the "Early Return" Pattern with the ? Operator
"Early Exit" (also called "Early Return") is the technique of immediately returning from a function (or exiting a loop) as soon as an invalid or exceptional condition is detected. For this purpose, Rust offers the ?↗ operator, which is essentially a shortcut for a match↗ expression:
fn func() -> Option<...> {
let maybe_val: Option<...> = ...;
let val = match maybe_val {
Some(v) => v,
_ => return None, // Early exit.
}
// ...
Some(val)
}
In the example below, when s.chars().next()? encounters None↗, the function is exited early (returning None), guarding the subsequent uppercase conversion logic from operating on an empty string.
This pattern removes the need for explicit, convoluted match↗ or if else↗ statements, making the "Happy Path" clearer to the reader.
//! The `?` operator shines when we have a sequence of operations, each of //! which might fail. It provides an early exit mechanism for functions that //! return `Option`. If any intermediate step returns `None`, the function //! immediately returns `None`. //! //! `?` works as well within functions that return `Result`. /// Attempt to retrieve the first character from the input string `s`. fn get_first_char_uppercase(input: Option<String>) -> Option<char> { // If `input` is `None`, the `?` operator will return `None` from this // function. If `input` is `Some`, it unwraps `input` and assigns it to // `s`. let s = input?; // If the string is empty, `chars().next()` returns `None`, indicating no // character is found. The `?` operator then immediately returns `None` // from the function, effectively propagating the absence of a character // up the call stack. let first_char = s.chars().next()?; // Convert the character to its uppercase equivalent. // `to_ascii_uppercase()` returns a char. Some(first_char.to_ascii_uppercase()) } fn main() { // `Option` contains a `String` and it's not empty. let s1 = Some("hello".to_string()); match get_first_char_uppercase(s1) { Some(c) => println!("First uppercase char: {c}"), // Output: H. None => println!("Could not process string."), } // `Option` contains an empty `String` (or is `None`). let s2 = Some("".to_string()); match get_first_char_uppercase(s2) { Some(c) => println!("First uppercase char: {c}"), None => println!("Could not process string."), /* Output: Could not * process string. */ } }
Work with the Contents of an Option
The following table provides an overview of the most commonly used Option↗ methods:
| Combinator | Description | Example Use Case |
|---|---|---|
is_some↗ | Returns true if the Option is Some, false otherwise. | Checking if a value exists without unwrapping it. |
is_none↗ | Returns true if the Option is None, false otherwise. | Checking if a value is absent. |
map↗ | Transforms the Some value into a new Option containing the result of applying a function f. If self is None, it remains None. | Converting a Some(String) to Some(usize) (e.g., length). |
and_then↗ | Chains operations that themselves return an Option. If self is Some(v), applies f to v and returns the resulting Option. If self is None, returns None. Prevents Option<Option<T>> (nesting). | Chaining database lookups where each step might return None (e.g., get_user_id.and_then(get_user_profile)). |
or↗ | Returns self if it is Some. Otherwise, returns other. Useful for providing a fallback Option. | Providing a default configuration if the primary configuration is missing. |
or_else↗ | Similar to or, but the fallback Option is computed by a closure f only if self is None. Useful for expensive fallback computations. | Fetching data from a backup source only if the primary source is unavailable. |
unwrap_or↗ | Returns the contained Some value, or a provided default value if self is None. | Getting a user-provided name or defaulting to "Guest". |
unwrap_or_else | Returns the contained Some value, or computes it from a closure f if self is None. Useful when the default value is expensive to compute. | Generating a new unique ID only if an existing one isn't found. |
ok_or↗ | Converts Option<T> into a Result<T, E>. If self is Some(v), returns Ok(v). If self is None, returns Err(err). | Converting an Option of a parsed number to a Result indicating success or a parsing error. |
ok_or_else↗ | Similar to ok_or, but the error value E is computed by a closure err_fn only if self is None. | Converting None to a detailed error message that needs to be generated. |
flatten↗ | Removes one level of nesting from an Option of an Option (i.e., Option<Option<T>> becomes Option<T>). | After a map operation that returns an Option, use flatten to simplify the result. |
filter↗ | Returns Some(t) if self is Some(t) AND the predicate function returns true. Otherwise, returns None. | Checking if a Some(age) is old enough to vote (`age.filter( |
zip↗ | If both self and other are Some, returns a Some containing a tuple of their values. Otherwise, returns None. | Combining Option<FirstName> and Option<LastName> into Option<(String, String)>. |
Extract the Value Contained in an Option with unwrap* and expect
The following methods extract the contained value in an std::option::Option↗ when it is the Some variant. If the std::option::Option↗ is None:
std::option::Option::unwrap↗ panics with a generic message.std::option::Option::expect↗ panics with a provided custom message.std::option::Option::unwrap_or↗ returns the provided default value.std::option::Option::unwrap_or_default↗ returns the default value of the type T (which must implement thestd::default::Default↗ trait).std::option::Option::unwrap_or_else↗ returns the result of evaluating the provided closure.
#![allow(clippy::unnecessary_literal_unwrap)] //! Demonstrates the use of `unwrap`, `expect`, `unwrap_or`, and //! `unwrap_or_else` with `Option`. /// `unwrap()` and `expect()` retrieve the inner value of a `Option`. /// BEWARE: if the `Option` is `None`, they will cause your program to panic. /// /// You should only use `unwrap()` or `expect()` when we are absolutely certain /// that the `Option` will be `Some`. fn unwrap_expect() { let x = Some(10); let _value = x.unwrap(); // `value` is 10. // This will panic! // let y: Option<i32> = None; // let _another_value = y.unwrap(); // `unwrap()` panics with a default message. // `expect("custom message")` panics with the specified custom message, // which is useful for debugging. // If calling `expect` is always safe, explain why in the message. let z = Some("A string"); let _value = z.expect("Can't panic: the `Option` is guaranteed to be `Some`."); } fn unwrap_or_unwrap_or_else() { let config_setting = Some(100); let _ = config_setting.unwrap_or(50); // 100. let config_setting: Option<u32> = None; let _ = config_setting.unwrap_or(50); // 50. let config_setting: Option<u32> = None; let _ = config_setting.unwrap_or_default(); // 0. // `unwrap_or_else` is useful when the default value is expensive to // compute, and we only want to compute it if the `Option` is `None`. let config_setting: Option<u32> = None; let _ = config_setting.unwrap_or_else(|| { println!("Calculating expensive default..."); 75 }); // 75. } fn main() { unwrap_expect(); unwrap_or_unwrap_or_else(); }
Transform Values Contained Within an Option
The following example demonstrates the use of the map↗, and_then↗ combinators and the unwrap_or_else↗ method.
The and_then and *or_else methods take a closure as input, and only evaluate the closure when they need to produce a new value.
use std::fs; /// Reads a file and returns its contents as `Some(String)` containing the /// trimmed, lower-case file contents if successful, otherwise `None`. fn read_file(filename: &str) -> Option<String> { fs::read_to_string(filename) // Convert a `Result` to `Option`. .ok() // `and_then` applies a function to the wrapped value if it's `Some`. .and_then(|contents| Some(contents.trim().to_string())) // `map` is similar, but the closure does not need to return an `Option`: .map(|s| s.to_lowercase()) } fn main() { let contents_maybe = read_file("temp/poem.txt"); // Provide a default with `unwrap_or_else` or `unwrap_or_default`. let contents = contents_maybe.unwrap_or_else(String::new); println!("{contents}"); }
Convert &Option<T> into Option<&T> or Similar
When working with references to Option, use:
std::option::Option::as_refandstd::option::Option::as_mutto convert from&Option<T>toOption<&T>andOption<&mut T>.std::option::Option::as_deref↗ to convert from&Option<T>toOption<&T::Target>.std::option::Option::as_deref_mut↗ to convert from&mut Option<T>toOption<&mut T::Target>.
See also the AsRef chapter.
fn as_ref() { let text: Option<String> = Some("Hello, world!".to_string()); // First, cast `Option<String>` to `Option<&String>` with `as_ref`, // then consume that with `map`, leaving `text` on the stack. let text_length: Option<usize> = text.as_ref().map(|s| s.len()); println!("We still can print `text`: {text:?}. Length: {text_length:?}",); } fn as_mut() { let mut x = Some(2); match x.as_mut() { Some(v) => { *v = 42; println!("x is Some({v})"); } None => { println!("x is None"); } } assert_eq!(x, Some(42)); } fn main() { as_ref(); as_mut(); }
See Also
- The ShowOption↗ crate provides extension methods and macros for formatting Options.
Related Topics
- Iterators.
- Match.
- Result.
- Rust Patterns.