Functional programming with Options and Results

In this previous chapter, we've seen how to use functional programming with iterators. In this chapter we'll see how to use functional programming with Option and Result.

Option

The Option type is used in Rust to represent a value that can be either something or nothing. The Option type is defined as follows:

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}
}

The Option type is generic, meaning that it can hold any type of value. The Some variant holds a value of type T, and the None variant represents the absence of a value.

It can be very useful to use a map method on an Option to transform the value inside the Option. If the Option contains a value, the map method applies the function to the value and returns a new Option containing the result. If the Option is None, the map method does nothing and returns None.

Let's look at an example:

fn main() {
    let maybe_number = Some(5);
    let maybe_number_plus_one = maybe_number.map(|number| number + 1);
    println!("{maybe_number_plus_one:?}"); // Some(6)

    let maybe_number = None;
    let maybe_number_plus_one = maybe_number.map(|number| number + 1);
    println!("{maybe_number_plus_one:?}"); // None
}

In a similar way you can also assign a default value to an Option using the unwrap_or method. If the Option contains a value, the unwrap_or method returns the value. If the Option is None, the unwrap_or method returns the default value.

fn main() {
    let maybe_number = Some(5);
    let number = maybe_number.unwrap_or(0);
    println!("{number:?}"); // 5

    let maybe_number = None;
    let number = maybe_number.unwrap_or(0);
    println!("{number:?}"); // 0
}

If the map operation returns an Option and you want to flatten the result, you can use the and_then method. The and_then method applies the function to the value inside the Option and returns the result. If the Option is None, the and_then method does nothing and returns None.

fn main() {
    let only_even_numbers = |number| -> Option<i32> {
        if number % 2 == 0 {
            Some(number)
        } else {
            None
        }
    };

    let maybe_even = Some(6).and_then(only_even_numbers);
    println!("{maybe_even:?}"); // Some(6)

    let maybe_odd = Some(5).and_then(only_even_numbers);
    println!("{maybe_odd:?}"); // None
}

Results

The Result type is used in Rust to represent a value that can be either a success or a failure. The Result type is defined as follows:

#![allow(unused)]
fn main() {
enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

The Result type is also generic, meaning that it can hold any type of success value and any type of error value. The Ok variant holds a success value of type T, and the Err variant holds an error value of type E. In that respect, Result is similar to Option, but with the added ability to hold an error value.

With that in mind, you can see how Result can be used in a similar way to Option. You can use a map method on a Result to transform the value inside the Result. If the Result contains a success value, the map method applies the function to the value and returns a new Result containing the result. If the Result is an error, the map method does nothing and returns the error.

fn main() {
    let maybe_number: Result<i32, &str> = Ok(5);
    let maybe_number_plus_one = maybe_number.map(|number| number + 1);
    println!("{maybe_number_plus_one:?}"); // Ok(6)

    let maybe_number: Result<i32, &str> = Err("error");
    let maybe_number_plus_one = maybe_number.map(|number| number + 1);
    println!("{maybe_number_plus_one:?}"); // Err("error")
}

Many of the operations that can be performed on Option can also be performed on Result. For example, you can use the unwrap_or method to assign a default value to a Result. If the Result contains a success value, the unwrap_or method returns the value. If the Result is an error, the unwrap_or method returns the default value.

Switching between Option and Result

You can use the ok method to convert an Option to a Result. If the Option contains a value, the ok method returns a Result containing the value. If the Option is None, the ok method returns a Result containing the default error value.

And vice versa, you can use the ok_or method to convert a Result to an Option. If the Result contains a success value, the ok_or method returns an Option containing the value. If the Result is an error, the ok_or method returns an Option containing the error value.

fn main() {
    let maybe_number = Some(5);
    let result = maybe_number.ok_or("error");
    println!("{result:?}"); // Ok(5)

    let maybe_number: Option<i32> = None;
    let result = maybe_number.ok_or("error");
    println!("{result:?}"); // Err("error")

    let result: Result<i32, &str> = Ok(5);
    let maybe_number = result.ok();
    println!("{maybe_number:?}"); // Some(5)

    let result: Result<i32, &str> = Err("error");
    let maybe_number = result.ok();
    println!("{maybe_number:?}"); // None
}

Reference material