---
title: "19.4 Iterator adapters"
description: "The lazy transformers: map, filter, enumerate, zip, rev, and chain build one iterator from another without doing any work until consumed."
url: "https://learnrust.net/chapter-19/iterator-adapters/"
last_updated: "2026-06-13"
---

# 19.4 Iterator adapters

An iterator produces values one at a time (lesson [19.3](https://learnrust.net/chapter-19/the-iterator-trait/)). An **adapter** is a method on an iterator that returns *another* iterator, a transformed one. Because adapters return iterators, you can chain them: each link in the chain takes values from the link before it and hands transformed values to the link after. And because iterators are lazy, building the whole chain costs nothing; the work happens only when a consumer (next lesson) pulls values through.

Adapters are where iterator code starts to read like a description of *what* you want rather than a recipe of *how* to loop. Let's meet the ones you'll use constantly. Every example here ends with `.collect()` into a `Vec` just so we can see the result; `collect` is properly covered in lesson [19.5](https://learnrust.net/chapter-19/consuming-iterators/), so for now read it as "run the pipeline and gather the output into a vector."

## map: transform each item

`map` applies a closure to every item, yielding the results:

```rust
fn main() {
    let nums = vec![1, 2, 3, 4];
    let squares: Vec<i32> = nums.iter().map(|x| x * x).collect();
    println!("{squares:?}");
}
```

```
[1, 4, 9, 16]
```

The closure runs once per item, and `map` yields whatever it returns. The output type can differ from the input type, mapping numbers to their string forms gives you an iterator of `String`:

```rust
let nums = vec![1, 2, 3];
let labels: Vec<String> = nums.iter().map(|n| format!("#{n}")).collect();
println!("{labels:?}");   // ["#1", "#2", "#3"]
```

## filter: keep some items

`filter` takes a closure returning `bool` and yields only the items for which it returns `true`:

```rust
fn main() {
    let nums = vec![1, 2, 3, 4, 5, 6];
    let evens: Vec<&i32> = nums.iter().filter(|x| *x % 2 == 0).collect();
    println!("{evens:?}");
}
```

```
[2, 4, 6]
```

One papercut to flag now, because it bites everyone. The closure gets `&&i32` here, a reference (from `filter`) to a reference (from `iter`), so it needs `*x` to reach the number for the `%`. Filtering and mapping references stacks up `&`s, and the dereferences are how you peel them. If you see a closure body sprouting `*`s, this is usually why; it's mechanical, not deep.

`map` and `filter` together are the bread and butter. "Square the odd numbers" is a `filter` then a `map`:

```rust
let nums = vec![1, 2, 3, 4, 5];
let odd_squares: Vec<i32> = nums.iter()
    .filter(|x| *x % 2 == 1)
    .map(|x| x * x)
    .collect();
println!("{odd_squares:?}");   // [1, 9, 25]
```

Read top to bottom, it's a sentence: take the numbers, keep the odd ones, square them, collect. The order matters, filtering before mapping squares only three values; the other arrangement would square all five and then filter, more work for the same answer.

## enumerate: pair each item with its index

`enumerate` wraps each item in a tuple `(index, item)`, counting from zero. This is the idiomatic answer to "I need the position too", far better than an external counter variable:

```rust
fn main() {
    let words = vec!["zero", "one", "two"];
    for (i, w) in words.iter().enumerate() {
        println!("{i}: {w}");
    }
}
```

```
0: zero
1: one
2: two
```

Notice this one drives the pipeline with a `for` loop instead of `collect`. Adapters feed consumers *and* `for` loops equally; a `for` over an adapter chain is completely normal.

## zip: walk two iterators in lockstep

`zip` pairs up the items of two iterators, yielding tuples, and stops as soon as *either* runs out:

```rust
fn main() {
    let names = vec!["Ada", "Alan", "Grace"];
    let ages = vec![36, 41, 45];
    let paired: Vec<(&str, i32)> = names.iter().copied()
        .zip(ages.iter().copied())
        .collect();
    println!("{paired:?}");
}
```

```
[("Ada", 36), ("Alan", 41), ("Grace", 45)]
```

(`.copied()` turns an iterator of `&T` into one of `T` for `Copy` types, here just so the tuples hold `&str` and `i32` rather than references; you'll meet it again next lesson.) The "stops at the shorter" rule is a feature: zipping a list with `0..` (an infinite counting iterator) is another way to get indices, pairing each item with 0, 1, 2, and the infinite side simply stops when the finite side does.

## rev and chain

`rev` reverses an iterator's direction (it works on iterators that can be walked from both ends, which includes vectors and ranges):

```rust
let countdown: Vec<i32> = (1..=5).rev().collect();
println!("{countdown:?}");   // [5, 4, 3, 2, 1]
```

`chain` glues two iterators end to end, yielding all of the first, then all of the second:

```rust
let a = vec![1, 2];
let b = vec![3, 4];
let both: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
println!("{both:?}");   // [1, 2, 3, 4]
```

These six, `map`, `filter`, `enumerate`, `zip`, `rev`, `chain`, plus the consumers next lesson, cover the large majority of real iterator code. There are dozens more in the standard library (`take`, `skip`, `step_by`, `flat_map`, `take_while`), and they all follow the same shape: a method on an iterator returning a new iterator. Once you've internalized the pattern, learning a new adapter is reading one line of documentation.

## Laziness, demonstrated

We claimed last lesson that adapters do nothing until consumed. Here's the proof, with two adapters and no consumer:

```rust
fn main() {
    let v = vec![1, 2, 3];
    let _pipeline = v.iter()
        .map(|x| { println!("mapping {x}"); x * 2 })
        .filter(|x| { println!("filtering {x}"); *x > 2 });

    println!("built the pipeline; ran nothing");
}
```

```
built the pipeline; ran nothing
```

Not one "mapping" or "filtering" line. The chain is fully built and then dropped, unconsumed, so zero closures run. Add a `.collect::<Vec<_>>()` to the end and you'd see the prints interleave per element (map 1, filter 2, map 2, filter 4, ...), because each value flows all the way through the chain before the next one starts. That per-element flow, not "all maps, then all filters", is the consequence of laziness, and it's what lets you chain a `take(3)` after an expensive `map` and have the map run only three times.

> **Key insight**
>
> An adapter chain is a *plan*, assembled lazily, that processes one element at a time when finally driven. This is why iterator pipelines are as fast as hand-written loops (lesson [19.7](https://learnrust.net/chapter-19/loops-vs-iterators/) measures it): there's no intermediate vector built between `map` and `filter`, each value passes through the whole chain in one go. You get the readability of "transform, then keep, then collect" with the performance of a single loop.

## Quiz time

**Question #1**

What distinguishes an *adapter* from a *consumer*?

<details class="solution">
<summary>Show solution</summary>

An adapter (like `map`, `filter`, `enumerate`, `zip`, `rev`, `chain`) returns a *new iterator*, so adapters are lazy and chainable, doing no work until driven. A consumer (like `collect`, `sum`, `for`) actually drives the iterator by calling `next` repeatedly, producing a final value or side effects. A chain of adapters with no consumer at the end does nothing.

</details>

**Question #2**

Write a pipeline that, given `vec![1, 2, 3, 4, 5, 6]`, produces a `Vec<i32>` of the squares of the even numbers.

<details class="solution">
<summary>Show solution</summary>

```rust
let nums = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = nums.iter()
    .filter(|x| *x % 2 == 0)
    .map(|x| x * x)
    .collect();
// [4, 16, 36]
```

Filter first (keep evens), then map (square them). Filtering before mapping does less work, since only the even values get squared. The `*x` in the filter peels the `&&i32` down to compare; mapping `x * x` over `&i32` works directly.

</details>

**Question #3**

`enumerate` is the clean way to get indices. How could you get the same `(index, item)` pairs using `zip` instead?

<details class="solution">
<summary>Show solution</summary>

Zip the iterator with an infinite counting range, putting the counter first: `(0..).zip(v.iter())` yields `(0, &v[0])`, `(1, &v[1])`, and so on. `zip` stops when the shorter side ends, and `v.iter()` is finite, so the infinite `0..` simply stops with it. `enumerate` is the purpose-built, more readable version of exactly this.

</details>

Adapters build pipelines but never finish them. The next lesson ([19.5](https://learnrust.net/chapter-19/consuming-iterators/)) covers the **consumers**, `collect`, `sum`, `count`, `fold`, `find`, `any`, `all`, that drive a pipeline and turn it into a value.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
