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

    #![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

    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

    #![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");
        }
    }
    ✓ Tests Rust test suite
    #[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 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