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 out of a list of key/value pairs.
pub fn read_optional_port(
pairs: Vec<(String, String)>,
key: String,
) -> Result<Option<u16>, String>
Three outcomes, and the signature encodes all three:
| situation | result |
|---|---|
| the key is absent |
Ok(None) |
present and parses as u16 |
Ok(Some(n)) |
| present and does not parse |
Err(…) |
The error message is exactly:
key `port` is not a valid port: eighty
— the key in backticks, then the original value (untrimmed) after the
colon. The value is trimmed before parsing, so " 443 " is Ok(Some(443)),
and the first matching key wins.
Read that table once more, because the signature is the lesson. “Missing” and
“invalid” are different things. A config reader that returns Err for a
setting the user simply did not set is a config reader that cannot have
defaults. Result<Option<T>, E> says “getting the answer can fail, and the
answer may legitimately be nothing” — two independent facts, two layers.
The starter’s E0308 is the whole problem in miniature
error[E0308]: mismatched types
|
| Ok(n) => Some(n),
| ^^^^^^^ expected `Result<Option<u16>, String>`,
| found `Option<u16>`
You have an Option from the lookup and a Result from the parse, and the
inner match produces one of each. Every beginner’s first instinct is to nest
matches until the types happen to line up, and it works, and it is four levels
deep for something that should be a sentence.
The natural intermediate value here is:
Option<Result<u16, String>>
“maybe there is a value, and if there is, parsing it may have failed”. Which is almost the return type, with the layers in the wrong order. Swapping them is a single method.
transpose, in both directions
let a: Option<Result<u16, String>> = Some(Ok(8080));
a.transpose() // Ok(Some(8080))
let b: Option<Result<u16, String>> = Some(Err("nope".into()));
b.transpose() // Err("nope")
let c: Option<Result<u16, String>> = None;
c.transpose() // Ok(None) <- the interesting one
let d: Result<Option<u16>, String> = Ok(Some(1));
d.transpose() // Some(Ok(1))
It exists on both types (Option::transpose since 1.33, Result::transpose
too) and the direction is genuinely confusing until you have seen the four
lines above. The one worth burning in is None.transpose() == Ok(None): an
absent value is not an error, so transposing it produces a successful “nothing”.
The rest of the bridge
Option → Result — ok_or(err) and ok_or_else(f). None becomes
Err(err), Some(v) becomes Ok(v). Same eager/lazy distinction as
unwrap_or: if building the error allocates (and a format! does), use
ok_or_else.
Result → Option — .ok() keeps the value, .err() keeps the error.
Clippy has two default-on lints aimed squarely at beginners here, and they are worth knowing before you write them:
error: called `ok().expect()` on a `Result` value
= help: you can call `expect()` directly on the `Result`
ok_expect and err_expect. r.ok().expect(msg) is a thing people write
because they are thinking “convert, then unwrap” — but going through Option
throws the error away first, so the panic message is worse than it would have
been. The direct r.expect(msg) prints your message and the error.
manual_ok_or is the third: it fires on opt.map_or(Err(e), Ok) and suggests
ok_or(e). Be aware that it only catches that one shape — the equivalent
hand-written match slips through. The gate here will not catch a match-based
solution for you, so this is a case where you have to hold yourself to the
standard: write the combinator chain.
Notes
-
70000does not fit in au16(max 65535), andu16::from_strreports that as aParseIntErrorjust like"eighty"does. Same handling, no special case. -
Present-but-empty (
"") is an error, not an absence. The distinction is real: an empty value in a config file is usually a typo, and silently treating it as unset hides the typo. -
iter_filter_is_okanditer_filter_is_someare the lints for.filter(Result::is_ok)followed by unwrapping — you wantflatten()orfilter_map, which you will meet properly when this track reaches iterators.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.