We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 23 of 24
unwrap and expect discipline
Take a function that works on good input and kills the process on anything else, and give its failures a return type.
pub fn process(rows: Vec<String>) -> Result<Vec<i32>, String>
Each row is key=value. Both halves are trimmed. Three ways a row can be bad,
and each one now has a message:
| row | error |
|---|---|
no = at all |
row `oops` has no `=` |
| empty (or whitespace-only) key |
row `=5` has an empty key |
| non-numeric value |
row `a=x` has a non-numeric value |
The message quotes the whole row, verbatim and untrimmed. On the first bad row, return; the rest are not processed.
The gate is five lints, all opt-in
#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unwrap_in_result,
clippy::panic_in_result_fn
)]
Every one of these is allow by default. Without that attribute this problem
would have no gate at all — the starter compiles, and clippy says nothing. That
is the honest situation with unwrap in Rust: nothing stops you, and nothing
will, unless a project turns these on deliberately.
With them on, the starter produces five errors:
error: used `unwrap()` on an `Option` value
error: used `expect()` on a `Result` value
error: `panic` should not be present in production code
error: `expect` used in a function that returns a `Result`
error: used `panic!()` or assertion in a function that returns `Result`
The last two are the interesting ones. unwrap_in_result and
panic_in_result_fn fire only inside a function that already returns
Result — which is a sharper rule than a blanket ban. If your signature
already says “this can fail”, panicking instead of returning Err is not a
judgement call, it is an inconsistency.
(A wart worth knowing: with unwrap_used denied, clippy suggests you use
expect instead — even when expect_used is also denied. That is
rust-clippy#9222, not
you misreading it.)
The real lesson is the message convention
A blanket ban on unwrap does not improve code; it just moves the panic to
.expect("failed"). What actually improves code is the convention std itself
uses:
The message states the invariant that was violated, phrased as what the code expected — not what went wrong.
// good: names the assumption, so when it fires you know what was wrong about it
.expect("hardcoded IP address should be valid")
.expect("cache should have been primed by init()")
.expect("config schema guarantees this key exists")
// bad: tells you nothing you could not see from the stack trace
.expect("failed to parse")
.expect("should work")
.unwrap()
An expect message is a comment the compiler cannot check but reality can. It
fires exactly when it is wrong, in front of the person who has to fix it. That
is worth more than the panic itself.
What the payloads actually look like
Verified on this toolchain:
| call | panic message |
|---|---|
Option::unwrap() |
called `Option::unwrap()` on a `None` value |
Option::expect(m) |
just m |
Result::unwrap() |
called `Result::unwrap()` on an `Err` value: <Debug of E> |
Result::expect(m) |
m: <Debug of E> |
So on Result, your message and the error’s Debug are both printed — which
is one more reason to derive Debug thoughtfully on error types, and why
r.ok().expect(m) is worse than r.expect(m): the .ok() throws away the
part that says what actually failed.
Two more lints in the family
-
panicking_unwrapis deny by default — you get it for free. It catchesif o.is_some() { o.unwrap() }shapes where the compiler can see the unwrap is redundant, and its siblingunnecessary_unwrapsuggestsif letinstead. -
get_unwrapfires onv.get(0).unwrap(), which is a longer way of writingv[0].
And a limit worth stating plainly: clippy does not catch every panic. Slice
indexing v[0] can panic and does not trip missing_panics_doc. Integer
division by zero panics. Arithmetic overflow panics in debug builds. Denying
unwrap_used buys you a lot, and it does not buy you a panic-free program.
Notes
-
split_once('=')splits at the first=, so"a=1=2"has value"1=2"— which is a non-numeric value, not a second key. -
ok_or_elseandmap_errare the two tools that turn the three panics into threeErrs. Both are lazy, so the messages cost nothing on the happy path. -
Under
-D warningsthe whole thing is a build failure rather than a warning, which is what these lints are for: they make a code-review comment into a compiler error.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.