Iterators

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Rust 1.99.0 and the 2024 edition, checked on 2026-10-06. An iterator produces a sequence of values one at a time. Iterators are central to idiomatic Rust: for loops desugar to them, collections expose them, and chains of adapters replace most hand-written index loops with code that is shorter, harder to get wrong, and — thanks to inlining — as fast as the loop it replaces.

The Iterator trait

An iterator is any type that implements Iterator, whose only required method is next:

pub trait IteratorLike {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ...plus about 75 provided methods: map, filter, collect, sum, ...
}

Each call to next returns Some(item) until the sequence is exhausted, then None. next takes &mut self because it advances internal state, so an iterator variable must be mut to call it directly.

fn main() {
    let titles = ["Dune", "Emma"];
    let mut it = titles.iter();
    assert_eq!(it.next(), Some(&"Dune"));
    assert_eq!(it.next(), Some(&"Emma"));
    assert_eq!(it.next(), None);
}

iter, iter_mut and into_iter

Collections offer three ways to iterate, which differ in what they yield and whether the collection survives:

Method Yields Effect on the collection

iter()

&T

borrowed; still usable afterwards

iter_mut()

&mut T

mutably borrowed; elements can be modified in place

into_iter()

T

consumed (moved) — unless called on a reference

A for loop calls IntoIterator::into_iter on its expression, so the three loop forms map to the three methods: for x in &v is v.iter(), for x in &mut v is v.iter_mut(), for x in v is v.into_iter(). IntoIterator is implemented for collections, for references to them, for arrays (by value since the 2021 edition), for ranges, and for every iterator (returning itself).

fn main() {
    let mut ratings = vec![3, 5, 4];
    for r in &ratings {           // &i32
        print!("{r} ");
    }
    for r in &mut ratings {       // &mut i32
        *r *= 2;
    }
    let total: i32 = ratings.iter().sum();
    let owned: Vec<String> = ratings.into_iter().map(|r| r.to_string()).collect(); // ratings consumed
    println!("{total} {owned:?}");
}

Laziness: adapters and consumers

Iterator methods come in two kinds:

  • Adapters (map, filter, take, …​) take an iterator and return a new one. They do no work when called; they just wrap the previous iterator.

  • Consumers (collect, sum, for_each, count, …​) repeatedly call next and produce a final value. Only then does the chain run.

Each value flows through the whole chain before the next value is pulled, so take(3) stops the upstream adapters after three items, even on an infinite source.

flowchart LR S["source
books.iter()"] --> F["filter
available?"] F --> M["map
title"] M --> T["take(3)"] T --> C["collect
Vec<&str>"] C -. "calls next()" .-> T T -. "calls next()" .-> M M -. "calls next()" .-> F F -. "calls next() until a match" .-> S
fn main() {
    let pipeline = (1..=10).map(|n| {
        println!("map {n}");
        n * n
    });
    println!("nothing has run yet");               // adapters are lazy
    let first_two: Vec<u32> = pipeline.take(2).collect(); // runs map only twice
    println!("{first_two:?}");

    let nums = [1, 2];
    nums.iter().map(|n| n + 1); // warning: unused `Map` that must be used -- nothing runs
}

Adapters

Adapter What it does

map(f)

transform each item

filter(pred)

keep items for which pred(&item) is true

filter_map(f)

map to Option and keep the Some values (map + filter in one)

flat_map(f) / flatten()

map each item to an iterator and concatenate them

enumerate()

pair each item with its index: (usize, T)

zip(other)

pair items from two iterators, stopping at the shorter

chain(other)

one iterator after another

skip(n), take(n), skip_while, take_while, map_while

drop or keep a prefix

step_by(n)

every n-th item

rev()

reverse (needs DoubleEndedIterator)

peekable()

allow looking at the next item without consuming it

scan(state, f)

like map with mutable state; can stop early by returning None

inspect(f)

look at each item (for debugging) and pass it on

cloned(), copied()

turn &T items into T

fuse()

guarantee None forever after the first None

map_windows (nightly), slice windows(n)/chunks(n)

overlapping or fixed-size groups (slices)

#[derive(Debug)]
struct Book {
    title: &'static str,
    year: u16,
    tags: Vec<&'static str>,
    available: bool,
}

fn main() {
    let books = [
        Book { title: "Dune", year: 1965, tags: vec!["sci-fi"], available: true },
        Book { title: "Emma", year: 1815, tags: vec!["classic", "romance"], available: false },
        Book { title: "Kindred", year: 1979, tags: vec!["sci-fi", "classic"], available: true },
    ];

    let available: Vec<&str> = books.iter().filter(|b| b.available).map(|b| b.title).collect();
    println!("{available:?}");                                     // ["Dune", "Kindred"]

    let years: Vec<u16> = ["1965", "x", "1979"].iter().filter_map(|s| s.parse().ok()).collect();
    println!("{years:?}");                                         // [1965, 1979]

    let mut tags: Vec<&str> = books.iter().flat_map(|b| b.tags.iter().copied()).collect();
    tags.sort_unstable();
    tags.dedup();
    println!("{tags:?}");                                          // ["classic", "romance", "sci-fi"]

    for (i, b) in books.iter().enumerate().skip(1) {
        println!("{i}: {}", b.title);
    }

    let shelves = ["A1", "A2"];
    let placed: Vec<_> = books.iter().map(|b| b.title).zip(shelves.iter().cycle()).collect();
    println!("{placed:?}");                                        // [("Dune", "A1"), ("Emma", "A2"), ("Kindred", "A1")]

    let running: Vec<u32> = [3, 1, 4].iter().scan(0, |acc, &x| { *acc += x; Some(*acc) }).collect();
    println!("{running:?}");                                       // [3, 4, 8]

    let mut words = "the dune the emma".split(' ').peekable();
    while let Some(w) = words.next() {
        if words.peek() == Some(&"dune") {
            println!("'{w}' comes before dune");
        }
    }

    let total_years: u32 = books
        .iter()
        .inspect(|b| println!("summing {}", b.title))             // debugging a pipeline
        .map(|b| u32::from(b.year))
        .sum();
    println!("{total_years}");
}

Consumers

Consumer Result

collect()

build any FromIterator collection

sum(), product()

numeric total (also into Option/Result)

count()

number of items

fold(init, f)

combine all items into one value, starting from init

reduce(f)

like fold, using the first item as the start; None if empty

min(), max(), min_by_key(f), max_by_key(f), min_by(cmp), max_by(cmp)

extremes

any(pred), all(pred)

short-circuiting boolean tests

find(pred), find_map(f), position(pred)

the first match (short-circuits)

last(), nth(n)

positional access

partition(pred)

split into two collections

unzip()

split an iterator of pairs into two collections

for_each(f), try_for_each(f)

run a closure for side effects

fn main() {
    let loans = [("alice", 3), ("bob", 12), ("carol", 0), ("dave", 7)];

    let total: u32 = loans.iter().map(|(_, d)| d).sum();
    let longest = loans.iter().max_by_key(|(_, d)| *d);
    let any_overdue = loans.iter().any(|(_, d)| *d > 10);
    let all_named = loans.iter().all(|(m, _)| !m.is_empty());
    let first_due = loans.iter().find(|(_, d)| *d == 0).map(|(m, _)| *m);
    let bob_at = loans.iter().position(|(m, _)| *m == "bob");
    let names = loans.iter().fold(String::new(), |acc, (m, _)| if acc.is_empty() { m.to_string() } else { acc + ", " + m });
    let max_days = loans.iter().map(|(_, d)| *d).reduce(u32::max);
    let (short, long): (Vec<_>, Vec<_>) = loans.iter().copied().partition(|(_, d)| *d < 5);
    let (members, days): (Vec<&str>, Vec<u32>) = loans.iter().copied().unzip();

    println!("{total} {longest:?} {any_overdue} {all_named} {first_due:?} {bob_at:?}");
    println!("{names} | {max_days:?} | {} short, {} long | {members:?} {days:?}", short.len(), long.len());
}

Azam (Advanced Rust, chapter 5) recommends inspect for debugging long chains and fold/reduce for aggregations, as above.

collect and FromIterator

collect builds any type implementing FromIterator<Item>, so the target type must be known — from an annotation, the turbofish, or the function’s return type:

use std::collections::{BTreeSet, HashMap};

fn main() {
    let pairs = [("Dune", 1965), ("Emma", 1815)];
    let v: Vec<_> = pairs.iter().map(|(t, _)| *t).collect();
    let map: HashMap<&str, u16> = pairs.iter().copied().collect();
    let set = pairs.iter().map(|(_, y)| y / 100).collect::<BTreeSet<_>>();
    let joined: String = ["a", "b", "c"].iter().copied().collect();       // String from &str items
    let chars: String = "dune".chars().rev().collect();
    let parsed: Result<Vec<u16>, _> = ["1965", "1815"].iter().map(|s| s.parse::<u16>()).collect();
    let (titles, years): (Vec<&str>, Vec<u16>) = pairs.iter().copied().collect(); // pairs: 1.79
    let (a, b, c): (Vec<u8>, Vec<u8>, Vec<u8>) = [(1, 2, 3), (4, 5, 6)].into_iter().collect(); // larger tuples: 1.85
    println!("{v:?} {map:?} {set:?} {joined} {chars} {parsed:?} {titles:?} {years:?} {a:?} {b:?} {c:?}");
}

Collecting into Result<Vec<T>, E> or Option<Vec<T>> stops at the first error or None; see Option, Result & Combinators.

Implementing Iterator

Implement next and you get every adapter and consumer for free. Override size_hint when you know the remaining length, and implement the extension traits when they apply: DoubleEndedIterator (next_back, enabling rev), ExactSizeIterator (len) and FusedIterator.

/// Iterates over the ISBN-13 check-weighted digits of a 12-digit prefix: (digit, weight).
struct WeightedDigits<'a> {
    digits: std::str::Bytes<'a>,
    index: usize,
}

impl<'a> WeightedDigits<'a> {
    fn new(prefix: &'a str) -> Self {
        Self { digits: prefix.bytes(), index: 0 }
    }
}

impl Iterator for WeightedDigits<'_> {
    type Item = (u32, u32);

    fn next(&mut self) -> Option<Self::Item> {
        let b = self.digits.next()?;
        let weight = if self.index.is_multiple_of(2) { 1 } else { 3 };
        self.index += 1;
        Some((u32::from(b - b'0'), weight))
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.digits.size_hint()
    }
}

impl ExactSizeIterator for WeightedDigits<'_> {}

fn main() {
    let it = WeightedDigits::new("978044101359");
    println!("{} digits", it.len());
    let sum: u32 = it.map(|(d, w)| d * w).sum();
    println!("check digit {}", (10 - sum % 10) % 10); // 3
}

Building iterators without a type

std::iter has constructors that save defining a struct:

use std::iter;

fn main() {
    let mut n = 0;
    let counter = iter::from_fn(|| { n += 1; (n <= 3).then_some(n) });  // from a closure
    println!("{:?}", counter.collect::<Vec<_>>());                       // [1, 2, 3]

    let doubling: Vec<u32> = iter::successors(Some(1u32), |x| x.checked_mul(2)).take(5).collect();
    println!("{doubling:?}");                                            // [1, 2, 4, 8, 16]

    let mut id = 100;
    let ids: Vec<u32> = iter::repeat_with(|| { id += 1; id }).take(3).collect();
    println!("{ids:?}");                                                 // [101, 102, 103]

    let header = iter::once("title").chain(["Dune", "Emma"]);
    println!("{:?}", header.collect::<Vec<_>>());
    println!("{:?}", iter::repeat_n("ab", 3).collect::<Vec<_>>());           // repeat_n: 1.82
    println!("{}", iter::empty::<u8>().count());
}

Zero-cost abstraction

Iterator chains compile to the same machine code as the equivalent hand-written loop: the adapters are generic structs, their next methods are inlined, and bounds checks on slices are removed because the iterator knows it stays in range. The Book’s chapter 13 benchmark shows the iterator version of a search as fast as the loop. Write the clearest chain and measure before "optimising" it into indexing. See Performance & Profiling.

Edition changes: Box<[T]> and gen

  • 2021: arrays implement IntoIterator by value, so [1, 2, 3].into_iter() yields integers, not references.

  • 2024: Box<[T]> implements IntoIterator by value as well; in earlier editions boxed_slice.into_iter() resolved to the slice’s by-reference iterator. cargo fix --edition rewrites affected calls to .iter().

  • 2024: gen is a reserved keyword, for future generator blocks (gen { yield 1; }) that would make writing iterators as easy as writing a function. They are nightly-only; until then, use iter::from_fn or a struct.

fn main() {
    let boxed: Box<[String]> = vec!["Dune".to_string(), "Emma".to_string()].into_boxed_slice();
    let owned: Vec<String> = boxed.into_iter().collect(); // 2024: yields String (by value)
    println!("{owned:?}");
}

Common errors

  • E0282 "type annotations needed" on collect: annotate the target (Vec<_>) or use the turbofish.

  • E0277 "a value of type X cannot be built from an iterator over elements of type &Y`": add `.copied()/.cloned(), or collect references.

  • E0596 calling next() on an iterator bound without mut.

  • E0382 using a collection after for x in collection consumed it: loop over &collection.

  • An unused adapter chain produces the unused_must_use warning "iterators are lazy and do nothing unless consumed".