On this page

Iterator and Closures

Iterator is Rust’s most successful abstraction—adaptors like map/filter/chain are lazy (not invoked, not executed until needed), and compile down to machine code equivalent to hand-written loops (zero-cost). The three closure traits Fn/FnMut/FnOnce correspond to three capture modes, and collect gathers iterators into containers—chained operations go from readable to zero-cost in one step.

The Iterator trait: The root of all iteration

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // 70+ methods have default implementations — all based on next()
}

next() is the only method that must be implemented. All other methods—map, filter, take, fold, collect—are default implementations based on next(). This means that as long as you implement next(), your type automatically gains access to the entire iterator ecosystem.

Lazy Evaluation: Iterators do nothing until "driven"

let v: Vec<i32> = (0..10)
    .filter(|x| x % 2 == 0)         // Does not happen — just builds the chain
    .map(|x| x * 2)                  // Does not happen
    .take(3)                         // Does not happen
    .collect();                      // Now! All adaptors are "driven"
// → [0, 4, 8]

This code does not process any elements until collect() is called. When collect() calls next(), it requests from take, which requests from map, which requests from filter, which requests from 0..10—forming a "pull" chain. This is fundamentally different from eager evaluation (such as Python list comprehensions).

Key Adaptors

adaptorPurposeWhen triggered
mapTransform each elementlazy
filterKeep elements that satisfy a conditionlazy
take(n)Take the first n elements then stoplazy
skip(n)Skip the first n elementslazy
enumerateAttach an index (index, item)lazy
chainConcatenate two iteratorslazy
flat_mapExpand each element into an iteratorlazy
foldAccumulate (reduce)eager (consumes the iterator)
collectCollect into a collectioneager

The Three Closure Traits: FnOnce → FnMut → Fn

These three traits form a constraint chain:

FnOnce: Can be called only once — consumes captured values (ownership transfer)
FnMut:  `&mut self` — can modify captured values, can be called multiple times
Fn:     `&self` — read-only capture, can be called multiple times, most general
  • All closures implement FnOnce by default.
  • If a closure does not consume captured values → it also implements FnMut.
  • If a closure does not modify captured values → it also implements Fn.
let s = String::from("hi");
let f = || s;                        // s is moved into the closure — this is a FnOnce
// f() can only be called once — after calling, s is consumed, and f can no longer be called

let mut v = vec![1, 2, 3];
let mut f = || { v.push(4); };       // Modifies v — FnMut (can be called multiple times)

let x = 42;
let f = || println!("{}", x);        // Read-only — Fn (most general)

Zero-cost Iteration

In release mode, iterator chains are typically "fused" by the compiler into a single loop—continuous calls to map, filter, collect are optimized into an equivalent for loop, with no extra allocations and no virtual function calls. This is how Rust’s zero-cost abstraction manifests in iterators.

References

  • Rust Book: Chapter 13
  • Rust Reference: Fn traits, Iterator

Keywords: Iterator, lazy evaluation, collect, Fn, FnMut, FnOnce, adaptor, zero-cost