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?