Iterators

Common Iterator Pitfalls

While Rust's iterator system prevents classic iterator invalidation at compile time, several subtle pitfalls around laziness, side effects, intermediate allocations, and reference layers commonly trip up developers.

1. Using .map() for Side Effects Without a Consumer

Because .map() is lazy, calling .map(|x| ...) without a consumer does nothing and triggers an unused_must_use warning. Even when followed by .collect::<Vec<()>>(), using .map() purely for side effects is an anti-pattern—use a for loop or .for_each() instead:

fn main() {
    let items = ["a", "b", "c"];

    // Correct: eager consumer `.for_each()` (or a standard `for` loop)
    items.iter().for_each(|item| print!("{item} "));
    println!();
}
a b c 

2. Unnecessary Intermediate .collect::<Vec<_>>() Calls

Calling .collect::<Vec<_>>() in the middle of an iterator pipeline forces an eager heap allocation between stages. Keep adapters chained lazily and call .collect() (or another consumer) only once at the very end:

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

    // Single lazy pipeline — zero heap allocations:
    let sum_of_evens: i32 = data
        .iter()
        .copied()
        .filter(|&x| x % 2 == 0)
        .map(|x| x * 10)
        .sum();

    println!("Sum: {sum_of_evens}");
}
Sum: 120

3. Modifying a Collection While Iterating Over It (E0502)

In languages without borrow checking, pushing or removing elements from a Vec while iterating over it can reallocate the backing buffer and cause a use-after-free crash. Rust prevents this at compile time with E0502:

// ❌ does not compile
fn main() {
    let mut nums = vec![1, 2, 3];
    for &n in &nums {
        if n == 2 {
            nums.push(4);
        }
    }
}
error[E0502]: cannot borrow `nums` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:13
  |
4 |     for &n in &nums {
  |               -----
  |               |
  |               immutable borrow occurs here
  |               immutable borrow later used here
5 |         if n == 2 {
6 |             nums.push(4);
  |             ^^^^^^^^^^^^ mutable borrow occurs here

To remove matching elements in place while iterating, use vec.retain(|x| ...) instead of a manual loop.

4. Collecting Result Elements Without Handling Short-Circuit Behavior

Remember the difference between collecting vs. flattening an iterator of Result<T, E>:

Pipeline Termination Behavior When an Err(e) Is Encountered
.collect::<Result<Vec<T>, E>>() Stops immediately at the first Err(e) and returns Err(e)
.flatten().collect::<Vec<T>>() Silently discards all Err values and collects all Ok(T) values
.partition::<Vec<_>, _>(Result::is_ok) Evaluates every element and separates all Oks from all Errs