We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 10 of 28
fold and try_fold: a total that refuses to overflow
Two functions over the same numbers.
pub fn checked_total(nums: Vec<i64>) -> Option<i64>
pub fn running_max(nums: Vec<i64>) -> Vec<i64>
-
checked_total— the sum of all the numbers, orNoneif the running total ever overflowsi64.[]isSome(0);[i64::MAX, 1]isNone. -
running_max— for each position, the largest value seen up to and including that position.[3, 1, 4, 1, 5]gives[3, 3, 4, 4, 5].[]gives[]. The output always has the same length as the input.
Why the naive total is wrong, and why nothing tells you
This binary is compiled with -O, which means overflow checks are off.
acc + x past i64::MAX does not panic here. It wraps, silently, to a
negative number. The starter does exactly that, and one of the visible test
cases reports:
expected total: null
actual total: -9223372036854775808
That is i64::MIN. The arithmetic did not fail — it produced a confidently
wrong answer. This is the single best argument for the checked_* family:
checked_add returns Option<i64>, None on overflow, in every build
profile. A debug build would have panicked and you would have found this in
testing; a release build would have shipped it.
The fold family
All five of these are consumers: they run the whole iterator and return a value, not another iterator.
| Method | Seed | Returns | Empty input |
|---|---|---|---|
fold(init, f) |
you supply |
B |
init |
reduce(f) |
first item |
Option<T> |
None |
sum() |
T::default() |
T |
zero |
product() |
one |
T |
one |
try_fold(init, f) |
you supply |
Option<B> / Result<B, E> |
Some(init) |
fold is the general one. Its signature is
fold<B, F: FnMut(B, Self::Item) -> B>(self, init: B, f: F) -> B — you thread
an accumulator of any type through every element. The accumulator does not
have to be a number. In running_max it is a Vec<i64>, which is a
perfectly ordinary thing for it to be and the reason fold is worth knowing
properly rather than as “the sum one”.
reduce has no seed — it takes the first element as the accumulator, so
it must return Option to describe an empty input. That is the whole
difference, and it matters: fold(0, ..) on [] gives 0, reduce(..) on
[] gives None. When there is no sensible identity element (the maximum of
no numbers is not zero), reduce is the honest choice.
try_fold: folding that can give up
fn try_fold<B, F, R>(&mut self, init: B, f: F) -> R
where F: FnMut(B, Self::Item) -> R, R: Try<Output = B>;
The closure returns something try-able — Option<B> or Result<B, E> —
and the moment it returns None or Err, try_fold stops and returns
that. It does not visit the remaining elements. It is the short-circuiting
fold, and it is the cleanest thing in std for “reduce, but any step may
fail”.
Here the closure is exactly |acc, x| acc.checked_add(x), which already has
the right shape. That is not a coincidence — checked_add was designed to
compose this way.
One wrinkle worth knowing: try_fold takes &mut self, not self, so the
iterator survives the call and can be used again — which is how the
short-circuiting consumers in item 9.18 are built on top of it.
The starter is rejected by clippy before you even get to the bug
Some(nums.into_iter().fold(0, |acc, x| acc + x))
error: this `.fold` can be written more succinctly using another method
[clippy::unnecessary_fold]
Default-on, so it is graded. fold(0, |a, b| a + b) is .sum(),
fold(1, |a, b| a * b) is .product(), fold(false, |a, b| a || b) is
.any(), fold(true, ..) is .all(). Clippy knows all four.
And note that you cannot fix checked_total with .sum(). .sum() uses
plain + and wraps exactly like the fold did. The overflow requirement is
what forces try_fold; there is no shortcut method that returns Option.
.sum() and the turbofish
While you are here: nums.iter().sum() on its own is
error[E0282]: type annotations needed
because Sum is implemented for many types and nothing says which one you
want. let t: i64 = nums.iter().sum(); or nums.iter().sum::<i64>(). Same
return-type-driven inference as parse in item 9.9.
running_max
Write it with fold and a Vec<i64> accumulator: each step looks at the
last element pushed so far, takes the larger of that and the current value,
and pushes it. Option::map_or handles the empty-so-far case in one
expression. (Item 9.15 shows the lazier scan-based version — but scan
is a different tool and this problem wants the fold.)
Grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.