Iterators
Prerequisites: Closures
An iterator produces a sequence of values one at a time. Every for loop
you have written uses one. Any type implementing the Iterator trait can be
iterated; the trait needs just one method:
trait Iterator { // simplified from the standard library
type Item; // the type of value produced
fn next(&mut self) -> Option<Self::Item>; // Some(value), or None when finished
}
On top of next, the trait provides dozens of methods for free.
Three ways to iterate a collection
| Method | Yields | The collection afterwards |
|---|---|---|
v.iter() |
&T |
unchanged, still usable |
v.iter_mut() |
&mut T |
still usable; items may have been modified |
v.into_iter() |
T |
moved: no longer usable |
for x in &v is shorthand for v.iter(), for x in &mut v for
v.iter_mut(), and for x in v for v.into_iter().
Adapters and consumers
Adapters such as map, filter, enumerate, zip, take, skip,
rev and chain wrap an iterator in a new one. Consumers such as
collect, sum, count, fold, max, min, find, any and all pull
values through and produce a result.
Iterators are lazy: adapters do nothing until a consumer asks for values. Each value flows through the whole chain before the next one starts, and no intermediate collections are created.
Syntax
fn main() {
let v = vec![1, 2, 3, 4];
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
let evens = v.iter().filter(|x| *x % 2 == 0).count();
let total: i32 = v.iter().sum();
println!("{doubled:?} {evens} {total}");
}
Example
struct Countdown {
remaining: u32,
}
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.remaining == 0 {
None
} else {
self.remaining -= 1;
Some(self.remaining + 1)
}
}
}
fn main() {
let prices = vec![12.5, 3.0, 8.25, 20.0];
let total: f64 = prices.iter().sum();
println!("total: {total}");
let cheap: Vec<f64> = prices.iter().copied().filter(|p| *p < 10.0).collect();
println!("cheap: {cheap:?}");
for (i, p) in prices.iter().enumerate().skip(2) {
println!("item {i}: {p}");
}
let names = ["ada", "grace", "linus"];
let ages = [36, 85, 54];
let people: Vec<String> = names
.iter()
.zip(ages.iter())
.map(|(name, age)| format!("{name} ({age})"))
.collect();
println!("{}", people.join(", "));
let mut scores = vec![1, 2, 3];
for s in scores.iter_mut() {
*s *= 10;
}
println!("scores: {scores:?}");
let product = (1..=5).fold(1, |acc, n| acc * n);
println!("5! = {product}");
let first_long = names.iter().find(|n| n.len() > 4);
println!("first long name: {first_long:?}");
// Laziness: nothing is printed until `collect` pulls values through.
let lazy = [1, 2, 3].iter().map(|n| {
println!(" processing {n}");
n * n
});
println!("before collect");
let squares: Vec<i32> = lazy.collect();
println!("squares: {squares:?}");
// Our own iterator gets every adapter for free.
let launch: Vec<u32> = Countdown { remaining: 5 }.filter(|n| n % 2 == 1).collect();
println!("odd countdown: {launch:?}");
}
Expected output
total: 43.75
cheap: [3.0, 8.25]
item 2: 8.25
item 3: 20
ada (36), grace (85), linus (54)
scores: [10, 20, 30]
5! = 120
first long name: Some("grace")
before collect
processing 1
processing 2
processing 3
squares: [1, 4, 9]
odd countdown: [5, 3, 1]
.copied() turns an iterator of &f64 into one of f64, which is convenient
for Copy types. For others, use .cloned() or iterate with into_iter().
Common mistakes
- Calling
collectwithout saying what to build:let v = nums.iter().map(|x| x * 2).collect();fails witherror[E0283]: type annotations needed. Annotate the variable (let v: Vec<i32>) or use the turbofish:.collect::<Vec<_>>(). - Building an adapter chain and never consuming it. The code does nothing and
the compiler warns
unused `Map` that must be used. Use aforloop for side effects. - Using a
Vecafterinto_iter()(or afterfor x in v):error[E0382]: borrow of moved value: `v`. Iterate over&vif you need it again. - Collecting into a
Veconly to loop over it again. Keep chaining adapters instead; it avoids allocating. - Summing without a type:
let s = v.iter().sum();also needs an annotation, such aslet s: i32.
Practice
Given let words = ["apple", "Bob", "kayak", "Rust", "level", "noon"];, use
an iterator chain to collect every palindrome (case-insensitive) into a
Vec<String> in upper case, then print it and its length.
Show solution
fn main() {
let words = ["apple", "Bob", "kayak", "Rust", "level", "noon"];
let palindromes: Vec<String> = words
.iter()
.map(|w| w.to_lowercase())
.filter(|w| w.chars().eq(w.chars().rev()))
.map(|w| w.to_uppercase())
.collect();
println!("{palindromes:?} ({} found)", palindromes.len());
}
["BOB", "KAYAK", "LEVEL", "NOON"] (4 found)