14.2 Using Option Types in Rust

14.2.1 Creating and Matching Option Values

Option values are created using Some or None and typically handled through pattern matching.

Example:

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(index) => println!("Found at index: {}", index),
        None => println!("Not found"),
    }
}

Output:

Found at index: 2

Recall that we covered pattern matching in detail in Chapter 10, where we also used the Option type in several examples.

14.2.2 Safe Unwrapping of Options

To access a value inside Option<T>, you must “unwrap” it. While methods like unwrap() extract the inner value, they cause a panic if used with None.

Using unwrap():

#![allow(unused)]
fn main() {
let some_value: Option<i32> = Some(5);
println!("{}", some_value.unwrap()); // Prints: 5
let no_value: Option<i32> = None;
// println!("{}", no_value.unwrap()); // Panics at runtime
}

Safer Alternatives:

  • unwrap_or(): Provides a default value if None.

    #![allow(unused)]
    fn main() {
    let no_value: Option<i32> = None;
    println!("{}", no_value.unwrap_or(0)); // Prints: 0
    }
  • expect(): Similar to unwrap(), but allows a custom panic message.

    #![allow(unused)]
    fn main() {
    let some_value: Option<i32> = Some(10);
    println!("{}", some_value.expect("Value should be present")); // Prints: 10
    }
  • Pattern Matching:

    #![allow(unused)]
    fn main() {
    let some_value: Option<i32> = Some(10);
    match some_value {
        Some(v) => println!("Value: {}", v),
        None => println!("No value found"),
    }
    }

14.2.3 Handling Option Types with the ? Operator

The ? operator, commonly used with Result types, can also streamline Option handling by returning None if the value is absent.

When used with an Option, the ? operator does the following:

  • If the Option is Some(value), it unwraps the value and allows the program to continue.
  • If the Option is None, it short-circuits the current function and returns None, effectively propagating the absence up the call stack.

Example:

fn get_length(s: Option<&str>) -> Option<usize> {
    let s = s?; // If `s` is None, return None immediately
    Some(s.len())
}
fn main() {
    let word = Some("hello");
    println!("{:?}", get_length(word)); // Prints: Some(5)
    let none_word: Option<&str> = None;
    println!("{:?}", get_length(none_word)); // Prints: None
}

This use of ? helps reduce boilerplate code and improves readability, especially when multiple Option values are involved in a function.

14.2.4 Useful Methods for Option Types

Rust's standard library provides a rich set of methods for working with Option types, such as map(), and_then(), unwrap_or_else(), and filter(), which simplify handling and transforming optional values.

  • map(): Transforms the contained value using a closure.

    #![allow(unused)]
    fn main() {
    let some_value = Some(3);
    let doubled = some_value.map(|x| x * 2);
    println!("{:?}", doubled); // Prints: Some(6)
    }
  • and_then(): Chains multiple computations that may return Option.

    #![allow(unused)]
    fn main() {
    fn multiply_by_two(x: i32) -> Option<i32> {
        Some(x * 2)
    }
    let value = Some(5);
    let result = value.and_then(multiply_by_two);
    println!("{:?}", result); // Prints: Some(10)
    }
  • unwrap_or_else(): Returns the contained Some value or computes it from a closure.

    #![allow(unused)]
    fn main() {
    let no_value: Option<i32> = None;
    let value = no_value.unwrap_or_else(|| {
        // Compute a default value
        42
    });
    println!("{}", value); // Prints: 42
    }
  • filter(): Filters the Option based on a predicate.

    #![allow(unused)]
    fn main() {
    let some_number = Some(4);
    let filtered = some_number.filter(|&x| x % 2 == 0);
    println!("{:?}", filtered); // Prints: Some(4)
    let another_number = Some(3);
    let filtered_none = another_number.filter(|&x| x % 2 == 0);
    println!("{:?}", filtered_none); // Prints: None
    }