🎓 A1Learner
🔍
0 of 149 lessons completed

Rust Fold and Reduce

fold is a consumer that builds an answer from a starting value. reduce is fold's close cousin with no starting value.

fold starts from a value you choose

The start value is the running total before any item arrives — here it begins at 100, not 0:

let total: i32 = vec![4, 8, 15].into_iter().fold(100, |acc, n| acc + n);
println!("{total}");

reduce starts from the first item

No start value is given. So the first item seeds the running value. An empty iterator has nothing to seed with:

let biggest: Option<i32> = vec![4, 8, 15].into_iter().reduce(|a, b| if a > b { a } else { b });
let none: Option<i32> = Vec::<i32>::new().into_iter().reduce(|a, b| a + b);
println!("{biggest:?} {none:?}");

unzip splits pairs apart

zip joined two streams into pairs. unzip takes pairs. It hands back the two streams:

let pairs = vec![("ada", 36), ("grace", 85)];
let (names, ages): (Vec<&str>, Vec<i32>) = pairs.into_iter().unzip();
println!("{names:?} {ages:?}");

find_map searches and converts together

find alone would hand back the first non-blank text. find_map also trims it on the way out. It does this in one pass:

let first_word: Option<String> = ["", "  ", "rust", "go"]
    .into_iter()
    .find_map(|s| {
        let t = s.trim();
        if t.is_empty() { None } else { Some(t.to_string()) }
    });
println!("{first_word:?}");

Example

fn main() {
    let scores = [72, 95, 40, 88];
    let total: i32 = scores.iter().fold(0, |acc, n| acc + n);
    println!("total = {total}");
    let summary: String = scores.iter().fold(String::new(), |mut acc, n| {
        acc.push_str(&format!("{n} "));
        acc
    });
    println!("summary = {}", summary.trim_end());
    let top: Option<i32> = scores.into_iter().reduce(|a, b| if a > b { a } else { b });
    println!("top = {top:?}");
    let results = [Ok(3), Err("bad row"), Ok(5)];
    let first_ok: Option<i32> = results.into_iter().find_map(|r| r.ok());
    println!("first ok = {first_ok:?}");
    let roster = [("ada", true), ("bo", false), ("cy", true)];
    let (present, absent): (Vec<(&str, bool)>, Vec<(&str, bool)>) =
        roster.into_iter().partition(|(_, here)| *here);
    println!("present = {present:?} absent = {absent:?}");
    let (names, _flags): (Vec<&str>, Vec<bool>) = roster.into_iter().unzip();
    println!("names = {names:?}");
}

Key points

  • Use fold(start, f) to build any answer.
  • The closure receives the running value and one item.
  • It returns the next running value
  • The start value sets the type of the answer.
  • Start with String::new() and the answer is text.
  • Start with 0 and it is a number.
  • The answer type may differ from the item type
  • Use reduce(f) when no start value is needed.
  • The first item becomes the running value.
  • So the answer is Option<Item>.
  • It is None when the iterator is empty
  • Use reduce for biggest or smallest.
  • Use it for any combine, too.
  • The answer has the item type there.
  • And an empty input honestly has no answer
  • Use unzip() to split an iterator of pairs into a pair of collections — the exact reverse of zip
  • Use find_map(f) to search and convert in one step.
  • It returns the first Some the closure produces.
  • It stops looking there

In this example, fold works twice with two different answer types. The first fold starts at 0 and adds the scores into a number: 295. The second starts with an empty String. It pushes each score in as text. The items are numbers. The answer is text. Only fold allows that change of shape. reduce then finds the top score with no start value. The first score, 72, seeds the running value. The answer is Some(95). The list is not empty. find_map walks the results and turns the first Ok into Some(3), skipping the error without a separate search step. partition splits the roster. It keeps whole pairs as it splits. unzip then pulls just the names out. It uses the same pairs.

Note

sum, product, min and max are folds and reduces the standard library wrote for you.

Reach for fold when no ready-made consumer fits the shape of your answer.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why does reduce return Option<T> while fold returns T directly?