9.0 What This Teaches
- The
Iteratortrait and itsnext()method - Creating iterators:
.iter(),.iter_mut(),.into_iter() - Lazy evaluation - adaptors do no work until consumed
- Consuming adaptors:
sum(),count(),collect(),for_each() - Transforming adaptors:
map(),filter(),copied() enumerate()andzip()- Chaining multiple adaptors
- Implementing
Iteratorfor a custom type
9.1 The Iterator Trait
Iterator trait requires exactly one method: next(). It
returns Some(item) while items remain and None when the
sequence is exhausted. Every other method on iterators - map,
filter, collect, and dozens more - is provided automatically,
built on top of next().
// Iterator trait - simplified view
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// all other methods are provided defaults built on next()
}
type Item is an associated type - it declares what kind of value the
iterator yields. A Vec<i32> iterator yields i32 (or
references to it, depending on how you create the iterator).
9.2 Creating Iterators
.iter()- borrows each element; yields&T; collection still usable after.iter_mut()- mutably borrows each element; yields&mut T.into_iter()- consumes the collection; yieldsTby value
// Iterators - creating iterators from a Vec
let v = vec![1, 2, 3];
for x in v.iter() { // x: &i32
println!("{x}");
}
println!("v still here: {v:?}"); // v is still owned here
let mut w = vec![1, 2, 3];
for x in w.iter_mut() { // x: &mut i32
*x *= 2;
}
println!("{w:?}"); // [2, 4, 6]
for x in v.into_iter() { // x: i32, v is consumed
println!("{x}");
}
// v cannot be used here
9.3 Consuming Adaptors
// Iterators - consuming adaptors
let nums = vec![1, 2, 3, 4, 5];
let total: i32 = nums.iter().copied().sum(); // 15
let count = nums.iter().count(); // 5
let doubled: Vec<i32> = nums.iter().map(|&x| x * 2).collect();
nums.iter().for_each(|x| println!("{x}"));
collect() is the most flexible consumer - it can gather items into a
Vec, HashMap, String, or any other collection
that implements FromIterator. The type annotation tells Rust which
collection to build.
9.4 map and filter
map transforms each element by applying a closure. filter
keeps only elements where the closure returns true. Both are lazy -
they produce a new iterator type and do no work until a consuming adaptor pulls from
the chain.
// Iterators - map and filter
let nums = vec![1, 2, 3, 4, 5, 6];
let evens_doubled: Vec<i32> = nums.iter()
.filter(|&&x| x % 2 == 0) // keep even elements
.map(|&x| x * 2) // double each one
.collect();
println!("{evens_doubled:?}"); // [4, 8, 12]
|&&x| in the filter
closure arises because .iter() yields &i32, and
filter passes a reference to that, giving &&i32.
Destructuring both levels with &&x gives the plain i32.
Using .copied() before filter avoids this.
// Iterators - using copied() to avoid double-reference patterns
let evens_doubled: Vec<i32> = nums.iter()
.copied() // &i32 -> i32
.filter(|&x| x % 2 == 0)
.map(|x| x * 2)
.collect(); // [4, 8, 12]
9.5 enumerate and zip
enumerate() pairs each element with its index, yielding
(usize, &T) tuples. zip() combines two iterators
element-by-element into pairs, stopping when the shorter one is exhausted.
// Iterators - enumerate and zip
let words = vec!["alpha", "beta", "gamma"];
for (i, w) in words.iter().enumerate() {
println!("{i}: {w}");
}
// 0: alpha
// 1: beta
// 2: gamma
let nums = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let pairs: Vec<_> = nums.iter().zip(letters.iter()).collect();
println!("{pairs:?}"); // [(1, 'a'), (2, 'b'), (3, 'c')]
9.6 Chaining Adaptors
// Iterators - chaining multiple adaptors
let data = vec![3, 1, 4, 1, 5, 9, 2, 6];
let result: Vec<i32> = data.iter()
.copied() // &i32 -> i32
.filter(|&x| x > 3) // keep values above 3
.map(|x| x * x) // square each value
.collect();
println!("{result:?}"); // [16, 25, 81, 36]
Vec is created between steps. Each element flows
through the full chain one at a time, allocated to the final Vec
only when collect() runs.
9.7 Implementing Iterator
Iterator. Only
next() is required. All adaptor and consumer methods become available
automatically because they are default methods on the trait.
// Iterators - custom iterator that counts from 1 up to a limit
struct Counter {
current: u32,
limit: u32,
}
impl Counter {
fn new(limit: u32) -> Self {
Counter { current: 0, limit }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.limit {
self.current += 1;
Some(self.current)
} else {
None
}
}
}
// all Iterator methods are now available for free
let values: Vec<u32> = Counter::new(5).collect();
println!("{values:?}"); // [1, 2, 3, 4, 5]
let total: u32 = Counter::new(5).sum();
println!("{total}"); // 15
let squares: Vec<u32> = Counter::new(4).map(|x| x * x).collect();
println!("{squares:?}"); // [1, 4, 9, 16]
9.8 Example - All Together
// Iterators - demonstrates creating, adapting, and consuming iterators in Rust.
struct Counter {
current: u32,
limit: u32,
}
impl Counter {
fn new(limit: u32) -> Self {
Counter { current: 0, limit }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.limit {
self.current += 1;
Some(self.current)
} else {
None
}
}
}
fn main() {
// sum via copied() + sum()
let v = vec![1, 2, 3, 4, 5];
let total: i32 = v.iter().copied().sum();
println!("sum = {total}");
// filter + map + collect
let evens_doubled: Vec<i32> = v.iter()
.copied()
.filter(|&x| x % 2 == 0)
.map(|x| x * 2)
.collect();
println!("evens doubled = {evens_doubled:?}");
// enumerate
let words = vec!["alpha", "beta", "gamma"];
for (i, w) in words.iter().enumerate() {
println!("{i}: {w}");
}
// zip
let nums = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let pairs: Vec<_> = nums.iter().zip(letters.iter()).collect();
println!("{pairs:?}");
// chaining
let data = vec![3, 1, 4, 1, 5, 9, 2, 6];
let result: Vec<i32> = data.iter()
.copied()
.filter(|&x| x > 3)
.map(|x| x * x)
.collect();
println!("{result:?}");
// custom iterator
let csum: u32 = Counter::new(5).sum();
println!("counter sum = {csum}");
}
sum = 15
evens doubled = [4, 8, 12]
0: alpha
1: beta
2: gamma
[(1, 'a'), (2, 'b'), (3, 'c')]
[16, 25, 81, 36]
counter sum = 15
9.9 Exercise
Exercise
- Count how many numbers in a
Vec<i32>are divisible by 3 using.iter().copied().filter(...).count(). - Convert a
Vec<&str>into aVec<String>using.map(|s| s.to_string()).collect(). - Implement a
Fibonacciiterator that yields the Fibonacci sequence indefinitely. Collect the first 10 values with.take(10).collect::<Vec<_>>().
9.10 Common Mistakes
Consuming the same iterator twice
let v = vec![1, 2, 3];
let it = v.iter();
let s: i32 = it.copied().sum();
let c = it.count(); // compile error: value used after move
Forgetting collect() - nothing runs
let v = vec![1, 2, 3];
v.iter().map(|&x| x * 2); // does nothing - no consumer
.collect(), .sum(), or another
consumer at the end, the chain never executes.
Double-reference pattern in filter closures
let v = vec![1, 2, 3];
// iter() yields &i32; filter adds another &, giving &&i32
v.iter().filter(|x| *x > 1); // x is &&i32, must deref
v.iter().filter(|&&x| x > 1); // destructure both layers
v.iter().copied().filter(|&x| x > 1); // or use copied() first
.copied() before filter to work with plain values.Using into_iter() when you still need the collection
let v = vec![1, 2, 3];
let _s: i32 = v.into_iter().sum();
println!("{v:?}"); // compile error: value borrowed after move
into_iter() consumes the collection. Use .iter().copied().sum()
if you need v afterward.
9.11 Key Terms
| Term | Meaning |
|---|---|
| Iterator | Trait with next() and all adaptor/consumer methods; core of Rust's iteration model |
| Item | Associated type on Iterator; the element type returned by next() |
| iter() | Creates a borrowing iterator yielding &T; source collection remains usable |
| iter_mut() | Creates a mutable-borrowing iterator yielding &mut T; allows in-place mutation |
| into_iter() | Consumes the collection and yields owned T values |
| adaptor | Method that transforms one iterator into another; lazy - does no work until consumed |
| consumer | Method that drives the iterator chain to completion (sum, collect, for_each) |
| lazy evaluation | Adaptors produce no output until a consumer pulls from the chain |
| copied() | Adaptor that converts &T to T for Copy types, simplifying closures |
| collect() | Consumer that gathers iterator output into a Vec, HashMap, or any FromIterator type |