We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 9 of 24
Converting between Option and Result
Read an optional setting from key/value pairs. Use transpose to preserve
the difference between a missing setting and an invalid value.
pub fn read_optional_port(
pairs: Vec<(String, String)>,
key: String,
) -> Result<Option<u16>, String>
Find the first pair whose key exactly matches key, then trim the value
and parse it as a u16. Matching is case-sensitive. Do not trim the key
or continue to another matching pair if the first value is invalid.
| Situation | Result |
|---|---|
| No matching key | Ok(None) |
Matching value parses as u16 |
Ok(Some(number)) |
| Matching value fails to parse | Err(message) |
Format an error exactly as follows, substituting the requested key and the original, untrimmed value:
key `port` is not a valid port: eighty
For key port, " 443 " succeeds with Ok(Some(443)), while
" eighty " produces an error that retains both spaces around eighty.
This exercise uses Rust's decimal u16 parsing rules. The accepted range
is 0 through 65535, and a leading + is allowed. Empty or whitespace-only
values, negative numbers, and overflow are errors. Port zero is accepted
here because it is a valid u16; this function does not apply additional
network configuration policy.
Two independent questions
Option records whether a setting was found. Result records whether
parsing succeeded. Returning Ok(None) leaves the caller free to choose
a default, while an error preserves the distinction between missing and
malformed input.
Mapping a parser over the lookup produces:
Option<Result<u16, String>>
The desired return type puts the possible error on the outside:
Result<Option<u16>, String>
transpose() converts between these arrangements without discarding the
number, error, or absence.
The three transpose cases
| Before: Option<Result<T, E>> | After: Result<Option<T>, E> |
|---|---|
Some(Ok(value)) |
Ok(Some(value)) |
Some(Err(error)) |
Err(error) |
None |
Ok(None) |
The reverse method, Result::transpose, maps each right-hand case back
to its left-hand case. This complete example checks both directions:
fn main() {
let cases: [Option<Result<u16, &str>>; 3] =
[Some(Ok(8080)), Some(Err("invalid")), None];
let expected = [Ok(Some(8080)), Err("invalid"), Ok(None)];
for (before, after) in cases.into_iter().zip(expected) {
assert_eq!(before.transpose(), after);
assert_eq!(after.transpose(), before);
}
}
Repair the starter
Its successful parse arm returns Some(number), but the function promises
Result<Option<u16>, String>. That mismatch causes E0308. The error arm
already has the required error shape.
A nested match could express the same behavior. For this exercise, practice
a chain: find the pair, map parsing over it, convert the parse error with
map_err, then transpose. The closure can borrow the trimmed value for
parsing while retaining the original string for the error message.
Choose conversions that preserve the information you need
ok_or(error) turns None into an error; ok_or_else computes the
error only when needed. They suit a required setting, but would give the
wrong missing-key behavior here.
Result::ok() discards an error and returns None. Using it on this
parse result would make malformed input indistinguishable from absence.
Result::err() does the reverse, retaining only an error.
A passing submission must compile, pass the tests, and pass
clippy -D warnings. Those checks do not by themselves require a
combinator chain; using transpose is the learning objective.
References: Option::transpose and u16 parsing.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.