14.2 Using Option Types in Rust
This section demonstrates how to create Option values, match on them, retrieve their contents safely, and use their helper methods.
14.2.1 Creating and Matching Option Values
To construct an Option, you call either Some(...) or use None. To handle both the present and absent cases, pattern matching is typical:
fn find_index(vec: &Vec<i32>, target: i32) -> Option<usize> { for (index, &value) in vec.iter().enumerate() { if value == target { return Some(index); } } None } fn main() { let numbers = vec![10, 20, 30, 40]; match find_index(&numbers, 30) { Some(idx) => println!("Found at index: {}", idx), None => println!("Not found"), } }
Output:
Found at index: 2
For more concise handling, you can use if let:
fn main() { let numbers = vec![10, 20, 30, 40]; if let Some(idx) = find_index(&numbers, 30) { println!("Found at index: {}", idx); } else { println!("Not found"); } }
14.2.2 Using the ? Operator
While the ? operator is commonly associated with Result, it also works with Option:
- If the
OptionisSome(value), thevalueis unwrapped. - If the
OptionisNone, the enclosing function returnsNoneimmediately.
fn get_length(s: Option<&str>) -> Option<usize> { let s = s?; // If s is None, return None early Some(s.len()) } fn main() { let word = Some("hello"); println!("{:?}", get_length(word)); // Prints: Some(5) let no_word: Option<&str> = None; println!("{:?}", get_length(no_word)); // Prints: None }
This makes code simpler when you have multiple optional values to check in succession.
14.2.3 Safe Unwrapping of Options
When you need the underlying value, you can call methods that extract it. However, you must do so carefully to avoid runtime panics.
unwrap()directly returns the contained value but panics onNone.expect(msg)is similar tounwrap(), but you can provide a custom panic message.unwrap_or(default)returns the contained value if present, ordefaultotherwise.unwrap_or_else(f)is likeunwrap_or, but instead of using a fixed default, it calls a closurefto compute the fallback.
Example: unwrap_or
fn main() { let no_value: Option<i32> = None; println!("{}", no_value.unwrap_or(0)); // Prints: 0 }
Example: expect(msg)
fn main() { let some_value: Option<i32> = Some(10); println!("{}", some_value.expect("Expected a value")); // Prints: 10 }
Example: Pattern Matching
fn main() { let some_value: Option<i32> = Some(10); match some_value { Some(v) => println!("Value: {}", v), None => println!("No value found"), } }
14.2.4 Combinators and Other Methods
Rust provides a variety of methods to make working with Option<T> more expressive and less verbose than raw pattern matches:
-
map(): Apply a function to the contained value if it’sSome.fn main() { let some_value = Some(3); let doubled = some_value.map(|x| x * 2); println!("{:?}", doubled); // Prints: Some(6) } -
and_then(): Chain computations that may each produce anOption.fn multiply_by_two(x: i32) -> Option<i32> { Some(x * 2) } fn main() { let value = Some(5); let result = value.and_then(multiply_by_two); println!("{:?}", result); // Prints: Some(10) } -
filter(): Retain the value only if it satisfies a predicate; otherwise produceNone.fn main() { let even_num = Some(4); let still_even = even_num.filter(|&x| x % 2 == 0); println!("{:?}", still_even); // Prints: Some(4) let odd_num = Some(3); let filtered = odd_num.filter(|&x| x % 2 == 0); println!("{:?}", filtered); // Prints: None } -
or(...)andor_else(...): Provide a fallback if the currentOptionisNone.fn main() { let primary = None; let secondary = Some(10); let result = primary.or(secondary); println!("{:?}", result); // Prints: Some(10) let primary = None; let fallback = || Some(42); let result = primary.or_else(fallback); println!("{:?}", result); // Prints: Some(42) } -
flatten(): Turn anOption<Option<T>>into anOption<T>(available since Rust 1.40).fn main() { let nested: Option<Option<i32>> = Some(Some(10)); let flat = nested.flatten(); println!("{:?}", flat); // Prints: Some(10) } -
zip(): Combine twoOption<T>values into a singleOption<(T, U)>if both areSome.fn main() { let opt_a = Some(3); let opt_b = Some(4); let zipped = opt_a.zip(opt_b); println!("{:?}", zipped); // Prints: Some((3, 4)) let opt_c: Option<i32> = None; let zipped_none = opt_a.zip(opt_c); println!("{:?}", zipped_none); // Prints: None } -
take()andreplace(...):take()sets theOption<T>toNoneand returns its previous value.replace(x)replaces the currentOption<T>with eitherSome(x)orNone, returning the old value.
fn main() { let mut opt = Some(99); let taken = opt.take(); println!("{:?}", taken); // Prints: Some(99) println!("{:?}", opt); // Prints: None let mut opt2 = Some(10); let old = opt2.replace(20); println!("{:?}", old); // Prints: Some(10) println!("{:?}", opt2); // Prints: Some(20) }