← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Rust Iterator Adapters

rust Intermediate
debt(d5/e3/b3/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5), clippy has lints for needless collect and unused iterators, but a side-effecting .map() that's never consumed can compile and silently do nothing — leaning toward d5 since clippy flags many but not all cases.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3), per quick_fix the correction is adding a terminal operation, reordering filter before map, or removing intermediate collect — small localised pattern changes within one pipeline.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3), iterator chains apply across all contexts (web/cli/queue/library) but each chain is self-contained; the choice does not structurally shape the system, the cost is per-pipeline.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7), the misconception that .map()/.filter() execute immediately contradicts eager semantics in most other languages' collection methods, so a competent developer coming from elsewhere will reliably guess wrong about laziness.

About DEBT scoring →

Also Known As

iterator combinators iterator chains lazy iterators

TL;DR

Iterator adapters are lazy combinators like map and filter that transform iterators without doing work until a consumer drives them.

Explanation

An iterator adapter is a method on the `Iterator` trait that takes an iterator and returns a new iterator wrapping it, such as `map`, `filter`, `take`, `skip`, `enumerate`, `zip`, and `flat_map`. Adapters are the building blocks of Rust's iterator pipelines, letting you express data transformations declaratively while the compiler fuses them into tight, allocation-free loops.

The single most important property of adapters is laziness. Calling `.map(...)` or `.filter(...)` does no work and touches no elements; it only constructs a new iterator type that remembers the operation. Nothing happens until a consuming method - a terminal operation such as `collect`, `sum`, `for_each`, `count`, `find`, or a `for` loop - pulls elements through the chain one at a time. This means a pipeline of ten adapters still walks the source once, pulling each element through every stage before moving to the next.

Because adapters are lazy and each returns a distinct concrete type (`Map<...>`, `Filter<...>`), the compiler can monomorphize and inline the whole chain, producing code as fast as a hand-written loop with no intermediate collections. This is why idiomatic Rust favors `iter().filter(...).map(...).collect()` over manual index loops: it is both clearer and equally fast.

Key adapters fall into groups. Transformers like `map` and `scan` reshape each item. Selectors like `filter`, `take_while`, and `skip_while` decide what passes. Combiners like `zip`, `chain`, and `flatten` merge sources. `enumerate` pairs items with their index. Adapters compose freely and most are themselves iterators, so order matters: `filter` before `map` avoids transforming items you will discard.

Common pitfalls stem from forgetting laziness. A `map` whose closure has side effects will never run if the iterator is never consumed - the compiler even warns that iterators are `#[must_use]`. Calling `collect` repeatedly allocates intermediate `Vec`s that defeat fusion. And consuming adapters by value vs borrowing via `iter`, `iter_mut`, or `into_iter` changes ownership semantics. Mastering adapters means internalizing that the chain is a recipe, not a result, until a terminal operation runs it.

Common Misconception

Calling .map() or .filter() immediately processes the collection. In reality adapters are lazy and do nothing until a terminal operation like collect or a for loop consumes the iterator.

Why It Matters

Forgetting laziness produces side-effecting maps that never run and pipelines that silently do no work, while needless collect calls reintroduce allocations the adapter chain was designed to avoid.

Common Mistakes

  • Calling .map() with a side-effecting closure but never consuming the iterator, so the closure never runs.
  • Inserting an intermediate .collect::<Vec<_>>() between adapters, defeating fusion and adding heap allocations.
  • Ordering .map() before .filter() so you transform elements you immediately discard.
  • Assuming .collect() infers the target type — forgetting the turbofish or type annotation, causing a 'type annotations needed' error.
  • Confusing iter(), iter_mut(), and into_iter(), borrowing when you meant to consume or vice versa.

Avoid When

  • You genuinely need an intermediate collection materialized because it is consumed multiple times or its length is required mid-pipeline.
  • The transformation is trivial and a plain loop reads more clearly to your team than a long adapter chain.
  • You must short-circuit with complex early-exit logic that adapters express awkwardly compared to an explicit loop.

When To Use

  • Expressing data transformations declaratively as map/filter/collect pipelines that fuse into allocation-free loops.
  • Processing large or streaming sequences lazily without building intermediate collections.
  • Composing reusable transformation steps where order can be tuned (filter before map) for efficiency.
  • Replacing manual index loops with clearer, equally fast iterator chains.

Code Examples

✗ Vulnerable
fn main() {
    let names = vec!["alice", "bob", "carol"];

    // Side-effecting map that is never consumed: nothing prints.
    names.iter().map(|n| {
        println!("processing {}", n);
        n.to_uppercase()
    });

    // Wasteful: map runs on every element, then filter throws most away,
    // and an intermediate Vec is allocated for no reason.
    let nums = vec![1, 2, 3, 4, 5, 6];
    let collected: Vec<i32> = nums.iter().map(|x| x * x).collect();
    let evens: Vec<i32> = collected.into_iter().filter(|x| x % 2 == 0).collect();
    println!("{:?}", evens);
}
✓ Fixed
    // Consume the iterator with collect so the closure (and its side effects) actually run.

Added 7 Jun 2026
Views 8
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 2 pings S 1 ping M 1 ping T 0 pings W
No pings yet today
Amazonbot 1
Google 2 Scrapy 1 Amazonbot 1
crawler 4
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Low
⚡ Quick Fix
Consume the chain with a terminal operation (collect, sum, for_each, for loop), order filter before map, and remove intermediate collect calls so adapters fuse.
📦 Applies To
web cli queue-worker library
🔗 Prerequisites
🔍 Detection Hints
\.(map|filter|flat_map|filter_map)\([^)]*\)\s*;|\.collect::<Vec<[^>]*>>\(\)\s*[^;]*\.(iter|into_iter)\(\)
Auto-detectable: ✓ Yes clippy
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Low Context: Function Tests: Update

✓ schema.org compliant