088 — Iterator Consumers
Tutorial
The Problem
Survey the terminal iterator operations that drive a lazy chain to produce a result: sum, product, count, collect, fold, min, max, any, all, find, and for_each. Demonstrate each with examples on ranges and slices, and compare with OCaml's Seq.fold_left-based equivalents.
🎯 Learning Outcomes
sum::<i32>() and product::<i32>() with turbofish type annotationcollect::<Vec<_>>() from collect::<String>() for different target typesfold as the universal consumer from which all others derivemin, max, any, all, find as short-circuiting consumersSeq.fold_left pattern in OCamlCode Example
#![allow(clippy::all)]
// 088: Iterator Consumers
// Terminal operations that drive the lazy chain
#[cfg(test)]
mod tests {
#[test]
fn test_sum() {
assert_eq!((1..=5).sum::<i32>(), 15);
}
#[test]
fn test_product() {
assert_eq!((1..=5).product::<i32>(), 120);
}
#[test]
fn test_count() {
assert_eq!((0..10).count(), 10);
}
#[test]
fn test_collect() {
let v: Vec<i32> = (0..3).collect();
assert_eq!(v, vec![0, 1, 2]);
}
#[test]
fn test_fold() {
assert_eq!((1..=5).fold(0, |acc, x| acc + x), 15);
}
#[test]
fn test_min_max() {
assert_eq!([3, 1, 4, 1, 5].iter().min(), Some(&1));
assert_eq!([3, 1, 4, 1, 5].iter().max(), Some(&5));
}
#[test]
fn test_any_all() {
assert!([1, 2, 3, 4].iter().any(|&x| x > 3));
assert!(![1, 2, 3].iter().any(|&x| x > 10));
assert!([1, 2, 3].iter().all(|&x| x > 0));
assert!(![1, 2, 3].iter().all(|&x| x > 2));
}
#[test]
fn test_collect_string() {
let s: String = vec!["a", "b", "c"].into_iter().collect();
assert_eq!(s, "abc");
}
}Key Differences
| Aspect | Rust | OCaml |
|---|---|---|
| Short-circuit | any/all/find stop early | fold_left always full scan |
sum type | Needs turbofish ::<T>() | Seq.fold_left (+) 0 s |
collect | Polymorphic via FromIterator | List.of_seq / Array.of_seq |
min/max | Built-in, returns Option<&T> | Custom fold with None accumulator |
for_each | iter.for_each(f) | Seq.iter f s |
fold | fold(init, f) | Seq.fold_left f init s |
Every lazy chain must end with a consumer. Choosing the right consumer is a code quality decision: collect when you need to store results, for_each for side effects, fold for aggregation, any/all when a boolean answer suffices. Avoid collect followed by indexing when a consumer suffices directly.
OCaml Approach
OCaml's Seq module provides fold_left, iter, and find. Custom consumers like seq_min, seq_max, seq_any, and seq_all are implemented in terms of fold_left with an Option accumulator for min/max. The Seq.fold_left is strict: it consumes the entire sequence. Short-circuiting requires exceptions or early-exit patterns. Both approaches achieve the same results; Rust's built-in short-circuit guarantees on any/all/find are language-level.
Full Source
#![allow(clippy::all)]
// 088: Iterator Consumers
// Terminal operations that drive the lazy chain
#[cfg(test)]
mod tests {
#[test]
fn test_sum() {
assert_eq!((1..=5).sum::<i32>(), 15);
}
#[test]
fn test_product() {
assert_eq!((1..=5).product::<i32>(), 120);
}
#[test]
fn test_count() {
assert_eq!((0..10).count(), 10);
}
#[test]
fn test_collect() {
let v: Vec<i32> = (0..3).collect();
assert_eq!(v, vec![0, 1, 2]);
}
#[test]
fn test_fold() {
assert_eq!((1..=5).fold(0, |acc, x| acc + x), 15);
}
#[test]
fn test_min_max() {
assert_eq!([3, 1, 4, 1, 5].iter().min(), Some(&1));
assert_eq!([3, 1, 4, 1, 5].iter().max(), Some(&5));
}
#[test]
fn test_any_all() {
assert!([1, 2, 3, 4].iter().any(|&x| x > 3));
assert!(![1, 2, 3].iter().any(|&x| x > 10));
assert!([1, 2, 3].iter().all(|&x| x > 0));
assert!(![1, 2, 3].iter().all(|&x| x > 2));
}
#[test]
fn test_collect_string() {
let s: String = vec!["a", "b", "c"].into_iter().collect();
assert_eq!(s, "abc");
}
}#[cfg(test)]
mod tests {
#[test]
fn test_sum() {
assert_eq!((1..=5).sum::<i32>(), 15);
}
#[test]
fn test_product() {
assert_eq!((1..=5).product::<i32>(), 120);
}
#[test]
fn test_count() {
assert_eq!((0..10).count(), 10);
}
#[test]
fn test_collect() {
let v: Vec<i32> = (0..3).collect();
assert_eq!(v, vec![0, 1, 2]);
}
#[test]
fn test_fold() {
assert_eq!((1..=5).fold(0, |acc, x| acc + x), 15);
}
#[test]
fn test_min_max() {
assert_eq!([3, 1, 4, 1, 5].iter().min(), Some(&1));
assert_eq!([3, 1, 4, 1, 5].iter().max(), Some(&5));
}
#[test]
fn test_any_all() {
assert!([1, 2, 3, 4].iter().any(|&x| x > 3));
assert!(![1, 2, 3].iter().any(|&x| x > 10));
assert!([1, 2, 3].iter().all(|&x| x > 0));
assert!(![1, 2, 3].iter().all(|&x| x > 2));
}
#[test]
fn test_collect_string() {
let s: String = vec!["a", "b", "c"].into_iter().collect();
assert_eq!(s, "abc");
}
}
Deep Comparison
Core Insight
Adapters are lazy; consumers are eager. A consumer pulls values through the chain and produces a final result. Without a consumer, the iterator chain does nothing.
OCaml Approach
Seq.fold_left is the universal consumerList.of_seq to collectSeq.iter for side effectsRust Approach
.sum(), .product(), .count() — specific consumers.collect() — universal collector.for_each() — side-effect consumer.fold() — general-purpose consumerComparison Table
| Consumer | OCaml | Rust |
|---|---|---|
| Sum | Seq.fold_left (+) 0 | .sum() |
| Collect | List.of_seq | .collect::<Vec<_>>() |
| Count | Seq.fold_left (fun n _ -> n+1) 0 | .count() |
| For each | Seq.iter f | .for_each(f) |
| Fold | Seq.fold_left f init | .fold(init, f) |
Exercises
max_by_key<T, K: Ord>(iter: impl Iterator<Item=T>, f: impl Fn(&T)->K) -> Option<T> using fold.scan (a stateful adapter) to produce a running sum from a range iterator.sum equals fold(0, Add::add) by implementing my_sum with fold and verifying equality.(1..=5) into a HashSet<i32> and a BTreeSet<i32> and compare membership test cost.seq_find : ('a -> bool) -> 'a Seq.t -> 'a option that short-circuits using an exception internally.