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, ...
}
Source: std::iter::Iterator
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 |
|---|---|---|
|
|
borrowed; still usable afterwards |
|
|
mutably borrowed; elements can be modified in place |
|
|
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 callnextand 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.
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 |
|---|---|
|
transform each item |
|
keep items for which |
|
map to |
|
map each item to an iterator and concatenate them |
|
pair each item with its index: |
|
pair items from two iterators, stopping at the shorter |
|
one iterator after another |
|
drop or keep a prefix |
|
every n-th item |
|
reverse (needs |
|
allow looking at the next item without consuming it |
|
like |
|
look at each item (for debugging) and pass it on |
|
turn |
|
guarantee |
|
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}");
}
Source: std::iter::Iterator
Consumers
| Consumer | Result |
|---|---|
|
build any |
|
numeric total (also into |
|
number of items |
|
combine all items into one value, starting from |
|
like |
|
extremes |
|
short-circuiting boolean tests |
|
the first match (short-circuits) |
|
positional access |
|
split into two collections |
|
split an iterator of pairs into two collections |
|
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());
}
Source: std: Iterator::fold
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:?}");
}
Source: std::iter::FromIterator
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
}
Source: std::iter: Implementing Iterator
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());
}
Source: std::iter: Functions
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
IntoIteratorby value, so[1, 2, 3].into_iter()yields integers, not references. -
2024:
Box<[T]>implementsIntoIteratorby value as well; in earlier editionsboxed_slice.into_iter()resolved to the slice’s by-reference iterator.cargo fix --editionrewrites affected calls to.iter(). -
2024:
genis 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, useiter::from_fnor 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
Xcannot be built from an iterator over elements of type&Y`": add `.copied()/.cloned(), or collect references. -
E0596 calling
next()on an iterator bound withoutmut. -
E0382 using a collection after
for x in collectionconsumed it: loop over&collection. -
An unused adapter chain produces the
unused_must_usewarning "iterators are lazy and do nothing unless consumed".