We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 22 of 24
Collecting an iterator of Results
Parse the same list twice — once fail-fast, once forgiving — and prove the fail-fast pass stopped early.
pub fn strict_and_lenient(raw: Vec<String>) -> (Result<Vec<i32>, String>, Vec<i32>, usize)
-
strict:
Result<Vec<i32>, String>— all the values, or the first failure as"bad input: {original}". -
lenient:
Vec<i32>— every value that parsed, skipping the rest. - calls: how many times the strict pass’s closure ran.
For ["1", "2", "x", "4", "5"] the answers are Err("bad input: x"),
[1, 2, 4, 5], and 3. Not 5. That third number is the whole point.
The best trick in this cluster
The standard library contains this impl:
impl<A, E, V: FromIterator<A>> FromIterator<Result<A, E>> for Result<V, E>
Read it slowly. If you can collect As into a V, then you can collect
Result<A, E>s into a Result<V, E>. So an iterator of results collects into
one result:
let strict: Result<Vec<i32>, String> = raw
.iter()
.map(|s| s.trim().parse::<i32>().map_err(|_| format!("bad input: {s}")))
.collect();
One line, and it short-circuits: collect stops pulling from the iterator
at the first Err, which is why the closure runs three times and not five.
That replaces a loop, an accumulator vector, an error slot, an if first_error.is_none(), and a final match — all of which the starter contains.
The same impl exists for Option:
let all: Option<Vec<i32>> = xs.iter().map(|s| s.parse().ok()).collect();
// None if any element was None
This is nearly impossible to discover by browsing the method list, because it is not a method — it is a trait impl on the target type. Once you know it exists you will use it constantly.
The lenient half
let lenient: Vec<i32> = raw.iter().filter_map(|s| s.trim().parse::<i32>().ok()).collect();
filter_map keeps the Somes and drops the Nones in one pass. Do not
write .filter(|r| r.is_ok()).map(|r| r.unwrap()) — clippy has
iter_filter_is_ok and iter_filter_is_some for exactly that, and the
unwrap is a panic waiting for a refactor. .flatten() works too:
Result and Option are both iterators of at most one item.
The type annotation is usually mandatory
let parsed = raw.iter().map(|s| s.parse::<i32>()).collect();
error[E0283]: type annotations needed
|
| let parsed = ... .collect();
| ^^^^^^ type must be known at this point
= note: cannot satisfy `_: FromIterator<Result<i32, ParseIntError>>`
collect is generic over its output, and there are many types it could
build — Vec<Result<_,_>>, Result<Vec<_>,_>, HashSet<_>, and so on. Rust
infers the return type from context when it can; when it cannot you get this
“type annotations needed” error and must say which. Either annotate the
binding (as above) or turbofish the call: .collect::<Result<Vec<_>, _>>().
This is also the reason the two collects in this problem behave so differently despite looking almost identical: the annotation is doing the work.
The neighbours
// sum and product also short-circuit through Result
let total: Result<i32, String> = items.iter().map(parse).sum();
// try_fold / try_for_each: short-circuiting folds
let n = items.iter().try_fold(0i32, |acc, x| acc.checked_add(*x).ok_or("overflow"))?;
And one trap. partition does not split results into values and errors:
let v: Vec<Result<i32, String>> = vec![Ok(1), Err("x".into()), Ok(3)];
let (a, b): (Vec<_>, Vec<_>) = v.into_iter().partition(Result::is_ok);
// a == [Ok(1), Ok(3)] NOT [1, 3]
// b == [Err("x")] NOT ["x"]
partition splits a collection into two collections of the same element
type. Getting (Vec<i32>, Vec<String>) needs a fold, or two passes with
filter_map.
Notes
-
The starter passes clippy and compiles cleanly. It is blocked purely by the
callscount — which is the honest gate here, because “did it stop early?” is a behavioural property no lint can see. -
An empty input collects to
Ok(vec![]). Emptiness is not failure. -
needless_collect(nursery) fires when you collect into aVeconly to immediately iterate it again. Worth knowing once your chains get longer.
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.