Iterators

Iterator Adapters and Performance

Iterator adapter chains in Rust are a prime example of a zero-cost abstraction: because each adapter is a generic struct parameterized over the concrete types of the inner iterator and closure, the compiler monomorphizes and inlines the entire chain into a single loop with no virtual calls or intermediate heap allocations.

How Adapter Chains Compile to a Single Loop

When you chain .iter().filter(p).map(f).sum(), the nested type is:

Map<Filter<std::slice::Iter<'_, i32>, ClosureP>, ClosureF>

Because every next() call in that nested struct is statically known, LLVM inlines Sum::sum -> Map::next -> Filter::next -> Iter::next and fuses them into a single machine loop equivalent to—or faster than—a hand-written for loop.

Bounds-Check Elimination and Auto-Vectorization (SIMD)

Iterating via slice iterators (slice.iter(), slice.chunks_exact(4), or std::iter::zip) often optimizes better than manual index loops (for i in 0..slice.len() { slice[i] }):

Aspect Manual Indexing (slice[i]) Slice Iterator (slice.iter())
Bounds checking Checked on slice[i] unless the optimizer proves i < slice.len() Pointer advances directly from ptr to end_ptr; no index bounds checks
Aliasing & SIMD Harder for LLVM to prove across multiple indexed slices chunks_exact and zip expose exact trip counts, enabling SIMD auto-vectorization
pub fn dot_product_iter(a: &[f32], b: &[f32]) -> f32 {
    a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum()
}

fn main() {
    let x = [1.0_f32, 2.0, 3.0, 4.0];
    let y = [2.0_f32, 3.0, 4.0, 5.0];
    println!("Dot product: {}", dot_product_iter(&x, &y));
}
Dot product: 40

Internal Iteration (fold / for_each) vs. External Iteration (next)

For chained or nested iterators such as Chain<A, B> or FlatMap, calling .next() repeatedly must check which inner state is active on every single element. By contrast, consumer methods built on internal iteration—.fold(), .try_fold(), .for_each(), and .sum()—run separate tight loops over A and then B without branching on state per element:

fn main() {
    let first = [1_i64, 2, 3];
    let second = [4_i64, 5, 6];

    // `.sum()` delegates to `.fold()`, exhausting `first` in one tight loop
    // and then `second` in a second tight loop without per-element state branches:
    let total: i64 = first.iter().chain(second.iter()).sum();
    println!("Chain sum: {total}");
}
Chain sum: 21

Pre-Allocation via size_hint and TrustedLen

When .collect::<Vec<T>>() is called on an iterator chain whose adapters preserve exact length (such as slice.iter().map(f).enumerate()), Vec::from_iter reads size_hint(), allocates the exact final capacity once, and writes elements directly into the buffer without intermediate resizing.