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 }