Skip to content

← Closures and Iterators step 10 of 28

Medium Primitives

fold and try_fold: a total that refuses to overflow

Implement two functions over a list of i64 values:

pub fn checked_total(nums: Vec<i64>) -> Option<i64>
pub fn running_max(nums: Vec<i64>) -> Vec<i64>

checked_total starts at zero and adds values from left to right. Return Some(total) if every addition fits in i64, or None at the first overflow in either direction. Empty input returns Some(0).

Check every intermediate total, not just the final mathematical sum. [i64::MAX, 1, -1] returns None: the second addition overflows even though the final mathematical sum would fit. A single i64::MIN or i64::MAX is valid and returns Some of that value.

running_max returns the largest value seen through each position, including the current value. It always returns one entry per input value, independently of whether checked_total fails.

Input Checked total Running maxima
[3, 1, 4, 1, 5] Some(14) [3, 3, 4, 4, 5]
[-1, -2] Some(-3) [-1, -1]
[] Some(0) []
[i64::MAX, 1] None [i64::MAX, i64::MAX]

Use try_fold with checked addition for the total, and fold with a Vec<i64> accumulator for the running maxima. Inputs may contain any i64 values, including duplicates; output vectors fit in memory.

Check addition explicitly

checked_add returns Some(sum) when the result fits, and None otherwise. Its overflow behavior is the same in debug and optimized builds.

Ordinary integer + does not implement this contract. Depending on overflow checks, it can panic or wrap instead of returning None. Optimization and overflow checks are separate compiler settings: -O alone does not rule out explicitly enabled checks. An ordinary sum::<i64>() does not report overflow as Option either.

Accumulate, or stop early

fold(init, f) passes an accumulator through the iterator in order. Each call receives the previous accumulator and the next item, then returns the new accumulator. On empty input it returns init. The accumulator type can differ from the item type, as it does for the vector in running_max.

try_fold lets a step signal failure. With Option, each successful step returns Some(next_accumulator); None stops the fold immediately. With Result, the corresponding outcomes are Ok(next_accumulator) and Err. Empty input returns Some(init) or Ok(init), according to the chosen type.

Here, acc.checked_add(x) already has the required Option<i64> shape. Use an i64 initial value so the accumulator's type is clear.

This complete example also shows how a named iterator can resume after a failed step. The item that caused failure has been consumed:

fn main() {
    let mut values = [i64::MAX, 1, -1].into_iter();
    let total = values.try_fold(0_i64, |acc, x| acc.checked_add(x));
    assert_eq!(total, None);
    assert_eq!(values.next(), Some(-1));
}

reduce is another option for an accumulator of the same type as the items: it uses the first item as its starting value and returns None when empty. Integer sum and product have empty results of zero and one, respectively. These methods do not all share one initialization rule; sum is defined through the Sum trait, not through Default.

Build the running maxima

Start the fold with an empty vector. For each value, compare it with the vector's last entry, push the larger one, and return the vector for the next step. When no previous entry exists, use the current input itself. Starting with zero would give wrong results for an all-negative input.

The starter's ordinary addition fold is also flagged by Clippy's unnecessary_fold lint. Replacing it with sum addresses that style issue but leaves the overflow contract unsatisfied. Use the required checked fold.

Your solution must compile, use the requested folds, match both outputs and pass Clippy with warnings treated as errors.