Working With Rust Result - Conversion to Option - Part 11
It’s often useful to convert a Result to its Option equivalent. Here are some functions that let us do that.
One thing to keep in mind is that an
Optionrepresents only a single type while aResultrepresents two types; one for success and one for error. Conversion to anOptionwill result in some data loss; either the success value or the error value will be lost in the conversion.
ok
ok maps an Ok instance to a Some instance and an Err instance to a None:
pub fn ok(self) -> Option<T> {
match self {
Ok(x) => Some(x),
Err(_) => None,
}
}In summary:
// pseudocode
// Given: Result<T, E>
// Result type: Option<T>
Ok(t:T) -> Some(t) // Option<T>
Err(_) -> None // Option<T>
For example, to only get a list of valid numbers from a list strings, we could use filter_map. filter_map filters and maps at the same time, only returning values that are wrapped in a Some constructor:
let maybe_numbers =
vec![
"1",
"2",
"three",
"4"
];
maybe_numbers
.iter()
.filter_map(|maybe_number| {
parse_number(maybe_number).ok()
})
.collect::<Vec<_>>(); // [1, 2, 4]err
This is the opposite of ok where we reverse the mappings, going from an Ok instance to None and an Err instance to
Some:
pub fn err(self) -> Option<E> {
match self {
Ok(_) => None,
Err(x) => Some(x),
}
}In summary:
// pseudocode
// Given: Result<T, E>
// Result type: Option<T>
Err(e:E) -> Some(e) // Option<E>
Ok(_) -> None // Option<E>
For example, if we only wanted invalid numbers from our list of possible numbers, we could use:
let only_errors =
maybe_numbers
.iter()
.filter_map(|maybe_number| {
parse_number(maybe_number)
.err()
.map(|e| (maybe_number, e)) // Return a pair of the "number" and the error
})
.collect::<Vec<_>>(); // [("three", ParseIntError { kind: InvalidDigit })]transpose
If we have a Result with a nested Option such as Result<Option<T>, E>, it’s useful to convert it to an Option with a nested Result such as Option<Result<T, E>>. We can achieve this with the transpose function which is defined as:
impl<T, E> Result<Option<T>, E> {
pub const fn transpose(self) -> Option<Result<T, E>> {
match self {
Ok(Some(x)) => Some(Ok(x)),
Ok(None) => None,
Err(e) => Some(Err(e)),
}
}
}In summary:
// pseudocode
// Given a Result<Option<T>, E>
// Result type: Option<Result<T, E>>
Ok(Some(t:T)) -> Some(Ok(t)) // Option<Result<T, E>>
Ok(None) -> None // Option<Result<T, E>>
Err(e:E) -> Some(Err(e)) // Option<Result<T, E>>
We are basically flipping the containers; going from Result<Option<T>, E> to a Option<Result<T, E>>.
But why is this useful?
This can be useful when you have a one or more Result<Option<T>, E>s and want to know if all the inner Option types are
valid Some instances. For example, to retrieve only even numbers or any parse errors we could use:
let maybe_numbers_2 =
vec![
"1",
"2",
"three",
"4",
"5",
"6",
"se7en",
];
maybe_numbers_2
.iter()
.filter_map(|maybe_number| {
parse_number(maybe_number)
.map(|n| {
if n % 2 == 0 {
Some(n) // We only want even numbers
} else {
None // We want odd numbers filtered out
}
})
.transpose()
})
.collect::<Vec<_>>() // [Ok(2), Err(ParseIntError { kind: InvalidDigit }), Ok(4), Ok(6), Err(ParseIntError { kind: InvalidDigit })]- Continue on to Value Tests
- Back to TOC