We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 8 of 24
Result combinators and map_err
Parse a list of strings as i32, double each one, and produce one output
string per input — naming the offending input when parsing fails.
pub fn parse_all(raw: Vec<String>) -> Vec<String>
-
"20"→"40" -
"12x"→"bad input: 12x" -
""→"bad input: "(the original string, empty, after the colon and space)
The whitespace rule is the one that catches people: str::parse trims for
you, so " 5 " parses to 5 — but the error message quotes the original
string, whitespace included: "bad input: x ".
Why this problem exists
std‘s parse error is ParseIntError, and its Display is exactly one of:
invalid digit found in string
cannot parse integer from empty string
number too large to fit in target type
Read those again. Not one of them tells you which string failed. That is not
an oversight — ParseIntError is produced deep inside std, which does not
know what your input was called, where it came from, or whether it is worth
mentioning. You know that. Attaching it is your job, and the tool for the
job is map_err.
s.trim().parse::<i32>() // Result<i32, ParseIntError>
.map_err(|_| format!("bad input: {s}")) // Result<i32, String>
map_err transforms the Err payload and leaves Ok untouched — the mirror
image of map. After ? itself, it is the most-used error-handling method in
real Rust, because every time you cross a boundary between two libraries you
have somebody else’s error type in hand and need your own.
The starter is blocked by a lint you should read carefully
error: manual implementation of `ok`
|
| let parsed = match s.trim().parse::<i32>() {
| ^ help: replace with: `s.trim().parse::<i32>().ok()`
clippy::manual_ok_err is on by default, and the obvious match therefore does
not survive the gate. But look at what the suggestion is: .ok() throws the
error away. The lint is telling you that if all you wanted was to discard the
error, there is a one-word way to say so — which makes it very obvious that
discarding is what you were doing, and here that is the bug.
The vocabulary
Transform — map(f) on the Ok, map_err(f) on the Err,
and_then(f) when your f is itself fallible, or_else(f) to recover.
Extract — unwrap_or(v), unwrap_or_else(f), unwrap_or_default(),
map_or(default, f), map_or_else(err_f, ok_f).
Convert to Option — .ok() keeps the value and drops the error;
.err() keeps the error and drops the value.
Ask — is_ok(), is_err(), is_ok_and(f), is_err_and(f).
Two asymmetries worth memorising, because the error messages when you get them wrong are confusing:
-
unwrap()needsE: Debug(it has to print the error it is panicking on), whileunwrap_err()needsT: Debug. E0277 with aDebug is not implementednote usually means you called one of these on a type you never derivedDebugfor. -
Result::flattenexists but is only stable since 1.89. If you are targeting anything older,and_then(|x| x)is the portable form.
The honest tension about map_err(|_| …)
Clippy has a restriction lint, map_err_ignore, that objects to
map_err(|_| …) on the grounds that you destroyed the underlying cause. It is
allow-by-default and it is not enabled here, but the objection is real:
-
At a boundary — a CLI printing a message to a human, an HTTP handler
producing a 400 — discarding the source is usually right. Nobody wants
invalid digit found in stringin a form validation message. -
In a library, discarding is usually wrong. Your caller may want to
distinguish “not a number” from “too large”, and once you have flattened it
to a
Stringthey cannot.
Later in this track you will keep both: a custom error type that carries the
original as its source(), so the message is yours and the cause survives.
Notes
-
map_unwrap_or(pedantic) is the lint that pushesr.map(f).unwrap_or(d)towardsr.map_or(d, f). Same meaning, one fewer intermediate. -
unnecessary_literal_unwrapfires onOk::<i32, ()>(3).unwrap()— unwrapping something you just built. It shows up in test code more than anywhere else. -
3.5and9999999999both fail to parse asi32— one is a float, one overflows — and both produce aParseIntError. Another reason the caller wants the input echoed back.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.