ExamplesBy LevelBy TopicLearning Paths
088 Intermediate

088 — Iterator Consumers

Functional Programming

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

  • • Understand that iterator adapters are lazy; consumers drive evaluation
  • • Use sum::<i32>() and product::<i32>() with turbofish type annotation
  • • Distinguish collect::<Vec<_>>() from collect::<String>() for different target types
  • • Use fold as the universal consumer from which all others derive
  • • Apply min, max, any, all, find as short-circuiting consumers
  • • Map each Rust consumer to the corresponding Seq.fold_left pattern in OCaml
  • Code 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

    AspectRustOCaml
    Short-circuitany/all/find stop earlyfold_left always full scan
    sum typeNeeds turbofish ::<T>()Seq.fold_left (+) 0 s
    collectPolymorphic via FromIteratorList.of_seq / Array.of_seq
    min/maxBuilt-in, returns Option<&T>Custom fold with None accumulator
    for_eachiter.for_each(f)Seq.iter f s
    foldfold(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]);
        }
    }
    ✓ Tests Rust test suite
    #[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 consumer
  • List.of_seq to collect
  • Seq.iter for side effects
  • Rust Approach

  • .sum(), .product(), .count() — specific consumers
  • .collect() — universal collector
  • .for_each() — side-effect consumer
  • .fold() — general-purpose consumer
  • Comparison Table

    ConsumerOCamlRust
    SumSeq.fold_left (+) 0.sum()
    CollectList.of_seq.collect::<Vec<_>>()
    CountSeq.fold_left (fun n _ -> n+1) 0.count()
    For eachSeq.iter f.for_each(f)
    FoldSeq.fold_left f init.fold(init, f)

    Exercises

  • Write max_by_key<T, K: Ord>(iter: impl Iterator<Item=T>, f: impl Fn(&T)->K) -> Option<T> using fold.
  • Use scan (a stateful adapter) to produce a running sum from a range iterator.
  • Show that sum equals fold(0, Add::add) by implementing my_sum with fold and verifying equality.
  • Collect (1..=5) into a HashSet<i32> and a BTreeSet<i32> and compare membership test cost.
  • In OCaml, implement seq_find : ('a -> bool) -> 'a Seq.t -> 'a option that short-circuits using an exception internally.
  • Open Source Repos