ExamplesBy LevelBy TopicLearning Paths
256 Intermediate

256: Chaining Iterators with chain()

Functional Programming

Tutorial

The Problem

Sequential processing of multiple separate collections is a universal programming need. Before lazy iterator composition, programmers allocated a new combined collection just to iterate over it — wasteful in both time and memory. The chain() combinator solves this by creating a single iterator that moves through one source, then continues with another, producing zero intermediate allocations. This is the functional programming principle of iterator composition: build complex traversal logic from small, single-purpose pieces.

🎯 Learning Outcomes

  • • Understand how chain() concatenates two iterators lazily with no intermediate allocation
  • • Recognize when to use chain() instead of concatenating into a new Vec
  • • Combine chain() with other adapters (map, filter, sum) in pipelines
  • • Chain more than two sources by applying chain() multiple times
  • Code Example

    #![allow(clippy::all)]
    //! 256. Chaining iterators with chain()
    //!
    //! `chain()` concatenates two iterators lazily — no allocation, just composition.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_chain_basic() {
            let a = [1i32, 2, 3];
            let b = [4i32, 5, 6];
            let result: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
            assert_eq!(result, vec![1, 2, 3, 4, 5, 6]);
        }
    
        #[test]
        fn test_chain_empty() {
            let a: Vec<i32> = vec![];
            let b = vec![1, 2];
            let result: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
            assert_eq!(result, vec![1, 2]);
        }
    
        #[test]
        fn test_chain_count() {
            let a = [1i32, 2, 3];
            let b = [4i32, 5];
            assert_eq!(a.iter().chain(b.iter()).count(), 5);
        }
    }

    Key Differences

  • Laziness: Rust's chain() is always lazy; OCaml's List.append / @ is strict and allocates immediately.
  • Type homogeneity: Both iterators must yield the same Item type in Rust; OCaml's polymorphic lists handle this naturally.
  • Source flexibility: Rust's chain() works on any Iterator implementation — slices, ranges, custom types — not only lists.
  • Lifetime tracking: Rust's borrow checker ensures chained references remain valid; OCaml relies on GC.
  • OCaml Approach

    OCaml uses List.append (the @ operator) for strict list concatenation, which copies the left spine. For lazy sequences, Seq.append is the true equivalent of chain():

    let chained = Seq.append (List.to_seq [1;2;3]) (List.to_seq [4;5;6])
    (* Lazy: nothing runs until consumed *)
    

    Full Source

    #![allow(clippy::all)]
    //! 256. Chaining iterators with chain()
    //!
    //! `chain()` concatenates two iterators lazily — no allocation, just composition.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_chain_basic() {
            let a = [1i32, 2, 3];
            let b = [4i32, 5, 6];
            let result: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
            assert_eq!(result, vec![1, 2, 3, 4, 5, 6]);
        }
    
        #[test]
        fn test_chain_empty() {
            let a: Vec<i32> = vec![];
            let b = vec![1, 2];
            let result: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
            assert_eq!(result, vec![1, 2]);
        }
    
        #[test]
        fn test_chain_count() {
            let a = [1i32, 2, 3];
            let b = [4i32, 5];
            assert_eq!(a.iter().chain(b.iter()).count(), 5);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_chain_basic() {
            let a = [1i32, 2, 3];
            let b = [4i32, 5, 6];
            let result: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
            assert_eq!(result, vec![1, 2, 3, 4, 5, 6]);
        }
    
        #[test]
        fn test_chain_empty() {
            let a: Vec<i32> = vec![];
            let b = vec![1, 2];
            let result: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
            assert_eq!(result, vec![1, 2]);
        }
    
        #[test]
        fn test_chain_count() {
            let a = [1i32, 2, 3];
            let b = [4i32, 5];
            assert_eq!(a.iter().chain(b.iter()).count(), 5);
        }
    }

    Exercises

  • Chain three slices of strings and collect all words into a single Vec<&str> without allocating an intermediate combined slice.
  • Use chain() to prepend a sentinel header element and append a footer element to an iterator of body items.
  • Build a function chain_n(slices: &[&[i32]]) -> Vec<i32> that chains an arbitrary number of slices using Iterator::flatten or repeated chain() calls.
  • Open Source Repos