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
chain() concatenates two iterators lazily with no intermediate allocationchain() instead of concatenating into a new Vecchain() with other adapters (map, filter, sum) in pipelineschain() multiple timesCode 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
chain() is always lazy; OCaml's List.append / @ is strict and allocates immediately.Item type in Rust; OCaml's polymorphic lists handle this naturally.chain() works on any Iterator implementation — slices, ranges, custom types — not only lists.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
Vec<&str> without allocating an intermediate combined slice.chain() to prepend a sentinel header element and append a footer element to an iterator of body items.chain_n(slices: &[&[i32]]) -> Vec<i32> that chains an arbitrary number of slices using Iterator::flatten or repeated chain() calls.