fold, sum, product and scan

A fold is the general consumer: it carries an accumulator from one item to the next and returns the final value. sum, product, count and max are the folds that the standard library writes for you, and scan is the adaptor that keeps every intermediate accumulator.

This lesson writes the general form first, then the named consumers, then the running total. Once the fold is clear, a loop that updates one accumulator can be read as a fold, and the compiler can optimise the iterator chain the same way. The last section is the type error for an accumulator that does not match the items.

fold

fold(initial, |accumulator, item| next) starts from the initial value and calls the closure once per item. The accumulator type is free, so the same method sums numbers or builds a string.

The program folds four numbers into a sum and the same numbers into a string. The two results are ten and the digits in order.

fn main() { let values = [1, 2, 3, 4]; let total = values.iter().fold(0, |sum, value| sum + value); let text = values.iter().fold(String::new(), |acc, value| format!("{acc}{value}")); println!("{total} {text}"); }

rustc --edition 2024 fold.rs
./fold

10 1234

The closure takes the accumulator by value and returns the next one, which is why the string version can use format! without a mutable binding. The lesson on adaptors shows where the iterator comes from.

sum and product

sum and product are folds with an identity and an operator, and both need the result type. The turbofish supplies it when the binding does not.

The program sums and multiplies three values. The sum is nine and the product is twenty-four.

fn main() { let values = [2, 3, 4]; println!("{}", values.iter().sum::<i32>()); println!("{}", values.iter().product::<i32>()); }

rustc --edition 2024 sum_product.rs
./sum_product

9
24

An empty iterator sums to zero and multiplies to one, because those are the identities of the two operations. A fold with a different initial value can express the same thing for a custom type.

A running total

scan is a lazy fold that yields the accumulator after every item. It returns an iterator, so it ends a chain only when a consumer such as collect runs it.

The program computes the running total of one, two, three and four. The output is the four partial sums, which is what a chart of a cumulative value needs.

fn main() { let values = [1, 2, 3, 4]; let running: Vec<i32> = values.iter().scan(0, |total, value| { *total += value; Some(*total) }).collect(); println!("{running:?}"); }

rustc --edition 2024 scan.rs
./scan

[1, 3, 6, 10]

The closure returns Some(total) for every item; returning None would stop the sequence, which is how a scan can also filter.

min, max and a key

min and max return an Option, because an empty iterator has no smallest value. min_by_key and max_by_key compare a derived value instead of the item.

The program finds the shortest word, the alphabetically largest and the alphabetically smallest of three words. The three answers are the words themselves, wrapped in Some.

fn main() { let words = ["pear", "fig", "banana"]; println!("{:?}", words.iter().min_by_key(|word| word.len())); println!("{:?}", words.iter().max()); println!("{:?}", words.iter().min()); }

rustc --edition 2024 minmax.rs
./minmax

Some("fig")
Some("pear")
Some("banana")

The key version is the one to use when the order is not the natural one, and it is stable for equal keys: the first item with the winning key wins. The lesson on sorting uses the same idea for a whole slice.

An accumulator of the wrong type

The initial value fixes the accumulator type, and every return of the closure must match it, so the compiler checks the whole fold from one value and one closure. A string initial value with a numeric closure is E0308, and the message shows the two types at the call.

The program folds numbers starting from an empty string. The error names the initial value and the closure, and the fix is a numeric initial value or a closure that returns a string.

fn main() { let values = [1, 2, 3]; let total: i32 = values.iter().fold("", |sum, value| sum + value); println!("{total}"); }

rustc --edition 2024 fold_err.rs

error[E0308]: mismatched types
--> fold_err.rs:3:41
|
3 | let total: i32 = values.iter().fold("", |sum, value| sum + value);
| ---- ^^ expected `i32`, found `&str`
| |
| arguments to this method are incorrect
|
help: the return type of this call is `&'static str` due to the type of the argument passed
--> fold_err.rs:3:22
|
3 | let total: i32 = values.iter().fold("", |sum, value| sum + value);
| ^^^^^^^^^^^^^^^^^^^--^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| this argument influences the return type of `fold`
note: method defined here
--> /rustc/31fca3adb283cc9dfd56b49cdee9a96eb9c96ffd/library/core/src/iter/traits/iterator.rs:2666:7

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.

A fold is also the shape a loop takes when it updates one variable, which is why the iterator version is usually shorter and has no index to get wrong. The same mismatch appears when the result is annotated with a type that the fold cannot produce. The annotation and the initial value are two places to fix, and the error points at both.

Exercises

  1. Fold a list of words into one sentence separated by spaces.
  2. Compute the running maximum of a list with scan and print each step.
  3. Find the longest word with max_by_key and the shortest with min_by_key.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does fold take besides the iterator?

    Answer

    An initial accumulator and a closure

    The initial value fixes the accumulator type, and the closure produces the next accumulator.

  2. What does sum return for an empty iterator?

    Answer

    The identity of addition, zero

    The empty sum is the identity, which is what makes the operation total.

  3. What does scan yield?

    Answer

    The accumulator after each item

    scan is the lazy form that keeps every intermediate value.

  4. Why does max return an Option?

    Answer

    An empty iterator has no maximum

    The absence of a value is a real case, so the result is an Option.

  5. What does this program print? fn main() { let values = [1, 2, 3, 4]; let total = values.iter().fold(0, |sum, value| sum + value); println!("{total}"); }

    Answer

    10

    The four values add up to ten, starting from zero.

  6. What does this program print? fn main() { let values = [2, 3, 4]; println!("{}", values.iter().sum::<i32>()); println!("{}", values.iter().product::<i32>()); }

    Answer

    9 / 24

    The sum is nine and the product is twenty-four.

  7. What does this program print? fn main() { let values = [1, 2, 3]; let running: Vec<i32> = values.iter().scan(0, |total, value| { *total += value; Some(*total) }).collect(); println!("{running:?}"); }

    Answer

    [1, 3, 6]

    The accumulator keeps the running total after each item.

  8. What does this program print? fn main() { let words = ["pear", "fig", "banana"]; println!("{:?}", words.iter().min_by_key(|word| word.len())); println!("{:?}", words.iter().max()); }

    Answer

    Some("fig") / Some("pear")

    The shortest word is fig, and the alphabetical maximum is pear.

  9. What is the first line of the compiler output for this program? fn main() { let values = [1, 2, 3]; let total: i32 = values.iter().fold("", |sum, value| sum + value); println!("{total}"); }

    Answer

    error[E0308]: mismatched types

    The initial value is a string and the annotation asks for an integer, so the types do not match.

  10. What does this program print? fn main() { let values = [1, 2, 3]; let text = values.iter().fold(String::new(), |acc, value| format!("{acc}{value}")); println!("{text}"); }

    Answer

    123

    The closure appends each number to the accumulator string.

The code on this page was built with rustc 1.96.1 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev