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
The starter function handles well-formed input, but on bad input it calls
unwrap, expect or panic!. Each of those panics: it stops the current
thread with an error message, and in a single-threaded program that usually
ends the program. Your job is to keep the signature and report each failure as
an Err value instead.
pub fn process(rows: Vec<String>) -> Result<Vec<i32>, String>
The exercise contract
Each row should have the form key=value. Process the rows in order:
- Split the row at its first
=. If the row contains no=, it is bad. - Trim whitespace from both ends of the key (with
str::trim, which removes Unicode whitespace). If the trimmed key is empty, the row is bad. - Trim the value the same way and parse it as an
i32withstr::parse. That accepts ASCII digits with an optional leading+or-, from -2147483648 through 2147483647. Anything else is bad, including an empty value,1.0,1_000, non-ASCII digits and numbers outside thei32range.
The checks run in that order, so a row such as =x reports the empty key, not
the value. If every row is good, return Ok with the parsed values in input
order. An empty input list is good and returns Ok(vec![]).
At the first bad row, return Err immediately with one of these messages
and do not look at later rows:
| problem | example row | exact error string |
|---|---|---|
no = |
oops |
row `oops` has no `=` |
| key empty after trimming | =5 |
row `=5` has an empty key |
value does not parse as i32 |
a=x |
row `a=x` has a non-numeric value |
In the messages, the row is copied exactly as it was given, before any
trimming, between backticks. For example, the row " =1" produces
row ` =1` has an empty key.
The gate: five Clippy lints that are off by default
Clippy is Rust's official collection of extra lints (warnings beyond the ones the compiler itself issues). The starter and the solution both begin with this attribute:
#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unwrap_in_result,
clippy::panic_in_result_fn
)]
All five lints belong to Clippy's restriction group, which is allowed (not
reported) by default. Without this attribute the starter compiles and
cargo clippy -- -D warnings reports nothing. Nothing in default Rust or
Clippy forbids unwrap; a project has to opt in to lints like these.
#![deny(...)] turns each listed lint into an error when Clippy runs, so with
the attribute the starter fails Clippy with five errors. Checked with Clippy
1.98, they are:
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 lints only apply inside a function whose return type is
Result. In that situation the signature already tells callers that the
function can fail, so the error belongs in the returned Result rather than in
a panic. On this toolchain unwrap_in_result reports unwrap and expect
calls on Result values, so here it flags the expect on the parse; the
unwrap on split_once, which returns an Option, is reported by
unwrap_used. panic_in_result_fn also reports assertion macros such as
assert!.
Writing useful expect messages
These lints do not mean expect is always wrong. Sometimes a value really
cannot be missing, for example when it was checked earlier or comes from a
constant, and expect documents that assumption. Banning unwrap alone does
not help much if the code just switches to .expect("failed").
The standard library documentation for Result::expect recommends phrasing
the message as the condition you expected to hold, often using the word
"should":
// Useful: each message names the assumption that turned out to be false.
.expect("hardcoded IP address should be valid")
.expect("cache should have been primed by init()")
.expect("config schema guarantees this key exists")
// Less useful: the panic location already says which call failed,
// and these messages add nothing about why it was expected to succeed.
.expect("failed to parse")
.expect("should work")
.unwrap()
A message written this way acts like a comment the compiler cannot check. If the assumption is ever false, the panic message tells the reader which assumption it was.
What the panic messages look like
Checked on Rust 1.98.1:
| call on a failing value | panic message |
|---|---|
Option::unwrap() |
called `Option::unwrap()` on a `None` value |
Option::expect(m) |
m |
Result::unwrap() |
called `Result::unwrap()` on an `Err` value: followed by the error's Debug output |
Result::expect(m) |
m: followed by the error's Debug output |
For a Result, the error value is printed with its Debug formatting after
your message, so a helpful Debug output on error types pays off here. This is
also why r.ok().expect(m) gives a worse message than r.expect(m): .ok()
converts the Result into an Option and discards the error, so only m is
printed.
Related lints
panicking_unwrapis in Clippy'scorrectnessgroup, which is denied by default. It reports anunwrapthat will always panic, such asif o.is_none() { o.unwrap() }.unnecessary_unwrap(warned by default) reports anunwrapafter a check that already proved it succeeds, such asif o.is_some() { o.unwrap() }, whereif let Some(x) = ois clearer.get_unwrap(arestrictionlint, off by default) reportsv.get(1).unwrap(), which panics in the same cases as indexing withv[1].
None of these lints finds every possible panic. Other operations panic too:
slice indexing out of bounds, integer division by zero, and arithmetic overflow
when overflow checks are enabled (the default in debug builds; release builds
wrap by default). Clippy has separate opt-in lints for some of these, such as
indexing_slicing. Others are not reported by the lints above. For example,
missing_panics_doc did not report a public function whose only possible panic
was v[1]. Denying unwrap_used removes one source of panics; it does not
make a program panic-free.
Notes
- Because the split is at the first
=, the rowa=1=2has keyaand value1=2, which does not parse, so it reports a non-numeric value. Option::ok_or_elseturnsNoneinto anErr, andResult::map_errreplaces the error inside anErr. Both take a closure that runs only on the failure path, so theformat!call that builds the message is skipped when the row is good.- The grade is: the code compiles, the tests pass, and
cargo clippy -- -D warningsreports nothing. The#![deny(...)]attribute already makes these five lints errors on its own;-D warningsadditionally turns every remaining warning into an error.
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.