Option<T> and Result<T, E> in Depth
transpose()
The .transpose() method swaps the outer and inner layers of a nested Option<Result<T, E>> and Result<Option<T>, E>, allowing you to use the ? error-propagation operator on optional fallible operations.
Signatures and Mapping Table
impl<T, E> Option<Result<T, E>> {
pub fn transpose(self) -> Result<Option<T>, E>;
}
impl<T, E> Result<Option<T>, E> {
pub fn transpose(self) -> Option<Result<T, E>>;
}
Option<Result<T, E>> |
$\Longleftrightarrow$ | Result<Option<T>, E> |
|---|---|---|
None |
$\Longleftrightarrow$ | Ok(None) |
Some(Ok(v)) |
$\Longleftrightarrow$ | Ok(Some(v)) |
Some(Err(e)) |
$\Longleftrightarrow$ | Err(e) |
Parsing an Optional Configuration Value (Option<&str> -> Result<Option<T>, E>)
A very common scenario is parsing an optional setting (such as an environment variable or CLI flag Option<&str>):
- If the setting is
None, the result should beOk(None)(not an error!). - If the setting is
Some(raw), parsingraw.parse::<T>()returnsResult<T, E>, soopt.map(str::parse)producesOption<Result<T, E>>. - Calling
.transpose()turnsOption<Result<T, E>>intoResult<Option<T>, E>, which can then be unwrapped with?:
use std::num::ParseIntError;
fn parse_optional_port(raw_port: Option<&str>) -> Result<Option<u16>, ParseIntError> {
// 1. `raw_port.map(str::parse)` has type `Option<Result<u16, ParseIntError>>`
// 2. `.transpose()` converts it to `Result<Option<u16>, ParseIntError>`
raw_port.map(str::parse::<u16>).transpose()
}
fn main() {
println!("None: {:?}", parse_optional_port(None));
println!("Some('443'): {:?}", parse_optional_port(Some("443")));
println!("Some('bad'): {:?}", parse_optional_port(Some("bad")));
}
None: Ok(None)
Some('443'): Ok(Some(443))
Some('bad'): Err(ParseIntError { kind: InvalidDigit })
Bridging Result<Option<T>, E> into an Iterator Pipeline (filter_map)
Conversely, when an iterator step (such as .filter_map(...)) expects an Option<Result<T, E>> (None to skip the item, Some(Ok(v)) to keep it, or Some(Err(e)) to propagate the error), calling .transpose() on a Result<Option<T>, E> flips it into the required Option<Result<T, E>>:
use std::num::ParseIntError;
fn parse_non_comment_line(line: &str) -> Result<Option<i32>, ParseIntError> {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
Ok(None)
} else {
Ok(Some(trimmed.parse::<i32>()?))
}
}
fn main() {
let lines = ["# header", "10", "", "25", "# footer"];
// `.transpose()` turns `Result<Option<i32>, E>` -> `Option<Result<i32, E>>` for `filter_map`:
let parsed: Result<Vec<i32>, ParseIntError> = lines
.iter()
.filter_map(|line| parse_non_comment_line(line).transpose())
.collect();
println!("Parsed numbers: {parsed:?}");
}
Parsed numbers: Ok([10, 25])