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
//! 088: Iterator Consumers
//!
//! Demonstrates various ways to consume iterators in Rust: basic consumers,
//! side-effecting iteration, and custom consumers implemented via `fold`.
/// Returns an iterator over the half-open range `[a, b)`.
pub fn range(a: i64, b: i64) -> impl Iterator<Item = i64> {
a..b
}
// ---------------------------------------------------------------------------
// Approach 1: Basic consumers
// ---------------------------------------------------------------------------
/// Sums all elements of an iterator of `i64`.
pub fn seq_sum<I: IntoIterator<Item = i64>>(iter: I) -> i64 {
iter.into_iter().sum()
}
/// Multiplies all elements of an iterator of `i64`.
pub fn seq_product<I: IntoIterator<Item = i64>>(iter: I) -> i64 {
iter.into_iter().product()
}
/// Counts the number of elements produced by the iterator.
pub fn seq_count<I: IntoIterator>(iter: I) -> usize {
iter.into_iter().count()
}
/// Collects all elements of the iterator into a `Vec`.
pub fn seq_collect<I: IntoIterator>(iter: I) -> Vec<I::Item> {
iter.into_iter().collect()
}
// ---------------------------------------------------------------------------
// Approach 2: for_each with side effects
// ---------------------------------------------------------------------------
/// Applies `f` to each element of the iterator for its side effects.
pub fn seq_for_each<I, F>(iter: I, f: F)
where
I: IntoIterator,
F: FnMut(I::Item),
{
iter.into_iter().for_each(f);
}
// ---------------------------------------------------------------------------
// Approach 3: Custom consumers
// ---------------------------------------------------------------------------
/// Returns the minimum element of the iterator, or `None` if empty.
pub fn seq_min<I>(iter: I) -> Option<I::Item>
where
I: IntoIterator,
I::Item: Ord,
{
iter.into_iter().fold(None, |acc, x| match acc {
None => Some(x),
Some(m) => Some(m.min(x)),
})
}
/// Returns the maximum element of the iterator, or `None` if empty.
pub fn seq_max<I>(iter: I) -> Option<I::Item>
where
I: IntoIterator,
I::Item: Ord,
{
iter.into_iter().fold(None, |acc, x| match acc {
None => Some(x),
Some(m) => Some(m.max(x)),
})
}
/// Returns `true` if any element of the iterator satisfies `pred`.
pub fn seq_any<I, F>(mut pred: F, iter: I) -> bool
where
I: IntoIterator,
F: FnMut(&I::Item) -> bool,
{
iter.into_iter().any(|x| pred(&x))
}
/// Returns `true` if every element of the iterator satisfies `pred`.
pub fn seq_all<I, F>(mut pred: F, iter: I) -> bool
where
I: IntoIterator,
F: FnMut(&I::Item) -> bool,
{
iter.into_iter().all(|x| pred(&x))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sum_of_range() {
assert_eq!(seq_sum(range(1, 6)), 15);
}
#[test]
fn product_of_range() {
assert_eq!(seq_product(range(1, 6)), 120);
}
#[test]
fn count_of_range() {
assert_eq!(seq_count(range(0, 10)), 10);
}
#[test]
fn collect_of_range() {
assert_eq!(seq_collect(range(0, 3)), vec![0, 1, 2]);
}
#[test]
fn min_of_vec() {
assert_eq!(seq_min(vec![3, 1, 4, 1, 5]), Some(1));
}
#[test]
fn max_of_vec() {
assert_eq!(seq_max(vec![3, 1, 4, 1, 5]), Some(5));
}
#[test]
fn min_max_of_empty() {
let empty: Vec<i64> = Vec::new();
assert_eq!(seq_min(empty.clone()), None);
assert_eq!(seq_max(empty), None);
}
#[test]
fn any_satisfied() {
assert!(seq_any(|x: &i64| *x > 3, vec![1, 2, 3, 4]));
}
#[test]
fn any_not_satisfied() {
assert!(!seq_any(|x: &i64| *x > 10, vec![1, 2, 3]));
}
#[test]
fn all_satisfied() {
assert!(seq_all(|x: &i64| *x > 0, vec![1, 2, 3]));
}
#[test]
fn all_not_satisfied() {
assert!(!seq_all(|x: &i64| *x > 2, vec![1, 2, 3]));
}
#[test]
fn for_each_side_effect() {
let mut collected = Vec::new();
seq_for_each(range(1, 4), |x| collected.push(x));
assert_eq!(collected, vec![1, 2, 3]);
}
}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
//! 088: Iterator Consumers
//!
//! Demonstrates various ways to consume iterators in Rust: basic consumers,
//! side-effecting iteration, and custom consumers implemented via `fold`.
/// Returns an iterator over the half-open range `[a, b)`.
pub fn range(a: i64, b: i64) -> impl Iterator<Item = i64> {
a..b
}
// ---------------------------------------------------------------------------
// Approach 1: Basic consumers
// ---------------------------------------------------------------------------
/// Sums all elements of an iterator of `i64`.
pub fn seq_sum<I: IntoIterator<Item = i64>>(iter: I) -> i64 {
iter.into_iter().sum()
}
/// Multiplies all elements of an iterator of `i64`.
pub fn seq_product<I: IntoIterator<Item = i64>>(iter: I) -> i64 {
iter.into_iter().product()
}
/// Counts the number of elements produced by the iterator.
pub fn seq_count<I: IntoIterator>(iter: I) -> usize {
iter.into_iter().count()
}
/// Collects all elements of the iterator into a `Vec`.
pub fn seq_collect<I: IntoIterator>(iter: I) -> Vec<I::Item> {
iter.into_iter().collect()
}
// ---------------------------------------------------------------------------
// Approach 2: for_each with side effects
// ---------------------------------------------------------------------------
/// Applies `f` to each element of the iterator for its side effects.
pub fn seq_for_each<I, F>(iter: I, f: F)
where
I: IntoIterator,
F: FnMut(I::Item),
{
iter.into_iter().for_each(f);
}
// ---------------------------------------------------------------------------
// Approach 3: Custom consumers
// ---------------------------------------------------------------------------
/// Returns the minimum element of the iterator, or `None` if empty.
pub fn seq_min<I>(iter: I) -> Option<I::Item>
where
I: IntoIterator,
I::Item: Ord,
{
iter.into_iter().fold(None, |acc, x| match acc {
None => Some(x),
Some(m) => Some(m.min(x)),
})
}
/// Returns the maximum element of the iterator, or `None` if empty.
pub fn seq_max<I>(iter: I) -> Option<I::Item>
where
I: IntoIterator,
I::Item: Ord,
{
iter.into_iter().fold(None, |acc, x| match acc {
None => Some(x),
Some(m) => Some(m.max(x)),
})
}
/// Returns `true` if any element of the iterator satisfies `pred`.
pub fn seq_any<I, F>(mut pred: F, iter: I) -> bool
where
I: IntoIterator,
F: FnMut(&I::Item) -> bool,
{
iter.into_iter().any(|x| pred(&x))
}
/// Returns `true` if every element of the iterator satisfies `pred`.
pub fn seq_all<I, F>(mut pred: F, iter: I) -> bool
where
I: IntoIterator,
F: FnMut(&I::Item) -> bool,
{
iter.into_iter().all(|x| pred(&x))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sum_of_range() {
assert_eq!(seq_sum(range(1, 6)), 15);
}
#[test]
fn product_of_range() {
assert_eq!(seq_product(range(1, 6)), 120);
}
#[test]
fn count_of_range() {
assert_eq!(seq_count(range(0, 10)), 10);
}
#[test]
fn collect_of_range() {
assert_eq!(seq_collect(range(0, 3)), vec![0, 1, 2]);
}
#[test]
fn min_of_vec() {
assert_eq!(seq_min(vec![3, 1, 4, 1, 5]), Some(1));
}
#[test]
fn max_of_vec() {
assert_eq!(seq_max(vec![3, 1, 4, 1, 5]), Some(5));
}
#[test]
fn min_max_of_empty() {
let empty: Vec<i64> = Vec::new();
assert_eq!(seq_min(empty.clone()), None);
assert_eq!(seq_max(empty), None);
}
#[test]
fn any_satisfied() {
assert!(seq_any(|x: &i64| *x > 3, vec![1, 2, 3, 4]));
}
#[test]
fn any_not_satisfied() {
assert!(!seq_any(|x: &i64| *x > 10, vec![1, 2, 3]));
}
#[test]
fn all_satisfied() {
assert!(seq_all(|x: &i64| *x > 0, vec![1, 2, 3]));
}
#[test]
fn all_not_satisfied() {
assert!(!seq_all(|x: &i64| *x > 2, vec![1, 2, 3]));
}
#[test]
fn for_each_side_effect() {
let mut collected = Vec::new();
seq_for_each(range(1, 4), |x| collected.push(x));
assert_eq!(collected, vec![1, 2, 3]);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sum_of_range() {
assert_eq!(seq_sum(range(1, 6)), 15);
}
#[test]
fn product_of_range() {
assert_eq!(seq_product(range(1, 6)), 120);
}
#[test]
fn count_of_range() {
assert_eq!(seq_count(range(0, 10)), 10);
}
#[test]
fn collect_of_range() {
assert_eq!(seq_collect(range(0, 3)), vec![0, 1, 2]);
}
#[test]
fn min_of_vec() {
assert_eq!(seq_min(vec![3, 1, 4, 1, 5]), Some(1));
}
#[test]
fn max_of_vec() {
assert_eq!(seq_max(vec![3, 1, 4, 1, 5]), Some(5));
}
#[test]
fn min_max_of_empty() {
let empty: Vec<i64> = Vec::new();
assert_eq!(seq_min(empty.clone()), None);
assert_eq!(seq_max(empty), None);
}
#[test]
fn any_satisfied() {
assert!(seq_any(|x: &i64| *x > 3, vec![1, 2, 3, 4]));
}
#[test]
fn any_not_satisfied() {
assert!(!seq_any(|x: &i64| *x > 10, vec![1, 2, 3]));
}
#[test]
fn all_satisfied() {
assert!(seq_all(|x: &i64| *x > 0, vec![1, 2, 3]));
}
#[test]
fn all_not_satisfied() {
assert!(!seq_all(|x: &i64| *x > 2, vec![1, 2, 3]));
}
#[test]
fn for_each_side_effect() {
let mut collected = Vec::new();
seq_for_each(range(1, 4), |x| collected.push(x));
assert_eq!(collected, vec![1, 2, 3]);
}
}
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.