title: Iterators
An iterator is anything that produces a sequence of values one at a time. Rust’s iterator system lets you process collections in a expressive, chainable way without writing manual loops.
Getting an iterator
let v = vec![1, 2, 3, 4, 5];
let iter = v.iter(); // yields &i32 (borrows elements)
let iter = v.iter_mut(); // yields &mut i32 (mutable borrows)
let iter = v.into_iter(); // yields i32 (consumes the vec, takes ownership)Most of the time you’ll use iter() for read-only access.
for loops use iterators under the hood
let v = vec![1, 2, 3];
for n in &v {
println!("{n}");
}This is syntactic sugar — Rust calls .into_iter() on &v behind the scenes.
Iterator adapters
Adapters transform an iterator into a new iterator. They are lazy — nothing runs until you consume the iterator.
map — transform each element
let doubled: Vec<i32> = vec![1, 2, 3]
.iter()
.map(|n| n * 2)
.collect();
// [2, 4, 6]filter — keep elements matching a condition
let evens: Vec<&i32> = vec![1, 2, 3, 4, 5]
.iter()
.filter(|n| *n % 2 == 0)
.collect();
// [2, 4]chain adapters together
let result: Vec<i32> = vec![1, 2, 3, 4, 5]
.iter()
.filter(|n| *n % 2 != 0) // keep odds
.map(|n| n * 10) // multiply by 10
.collect();
// [10, 30, 50]Consuming the iterator
Adapters are lazy — you need a consumer to actually run the chain and produce a result.
collect — gather into a collection
let v: Vec<i32> = (1..=5).collect(); // [1, 2, 3, 4, 5]sum and product
let total: i32 = vec![1, 2, 3, 4, 5].iter().sum(); // 15count
let n = vec![1, 2, 3].iter().count(); // 3any and all
let has_even = vec![1, 2, 3].iter().any(|n| n % 2 == 0); // true
let all_positive = vec![1, 2, 3].iter().all(|n| *n > 0); // truefind — first matching element
let first_even = vec![1, 2, 3, 4].iter().find(|n| *n % 2 == 0);
// Some(2)Returns an Option — None if nothing matches.
Ranges as iterators
Ranges are iterators too:
for i in 0..5 {
println!("{i}"); // 0, 1, 2, 3, 4
}
let squares: Vec<i32> = (1..=5).map(|n| n * n).collect();
// [1, 4, 9, 16, 25]Lazy vs eager
Iterator adapters (.map(), .filter(), .take(), etc.) are lazy — they return a new iterator type and do no work yet. You can keep chaining off them.
Iterator consumers (.collect(), .sum(), .count(), .for_each(), etc.) are eager — they drive the chain to completion and produce a concrete result. You can’t chain further off them.
A quick test: if you can keep chaining, it’s lazy. If it gives you a final value, it’s eager.
vec![1, 2, 3]
.iter() // lazy — returns Iter<i32>
.map(|n| n * 2) // lazy — returns Map<...>
.filter(|n| n > 2) // lazy — returns Filter<...>
.collect::<Vec<_>>() // eager — drives the chain, returns Vec<i32>Why laziness matters
Without laziness, each step would allocate an intermediate vector:
// hypothetical eager approach — 3 allocations
let mapped: Vec<i32> = input.map(...); // allocates 1M elements
let filtered: Vec<i32> = mapped.filter(...); // allocates again
let result = filtered.collect(); // allocates againWith lazy chaining, each element flows through the entire chain one at a time — only one allocation at the end:
// actual Rust — 1 allocation
let result: Vec<i32> = input.iter().map(...).filter(...).collect();This also means a chain like this never processes any elements at all:
let _ = vec![1, 2, 3].iter().map(|n| n * 2); // nothing happens, no consumerAnd early-terminating consumers stop the chain as soon as they have what they need:
let first = (0..).find(|n| n * n > 100); // stops at 11, never iterates further