19.3The Iterator trait
You've been iterating since chapter 7. for x in &scores walks a vector; for c in word.chars() walks the characters of a string. Underneath every one of those loops is a single small trait, and once you see it, a huge amount of the standard library stops being magic. An iterator is any value that knows how to produce a sequence of items, one at a time, on request. That's the entire idea. The trait that captures it is Iterator, and it has essentially one required method.
The trait itself
Stripped to its core, Iterator looks like this:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Two pieces. type Item is an associated type: a type that belongs to the trait and that each implementor fills in, naming the kind of thing this particular iterator yields (i32, &String, char). It's written type Item; in the trait and type Item = i32; in an implementation, and you read Self::Item as "whatever this iterator decided to yield." And next is the engine: call it, and it returns Some(item) with the next value, or None once the sequence is exhausted. The Option return (chapter 11) is how an iterator says "I'm done", there's no separate "are we finished?" call, the None is the signal.
Because next takes &mut self, calling it changes the iterator: it advances. An iterator is a stateful little machine that remembers where it is. Let's watch one run by hand:
fn main() {
let v = vec![10, 20, 30];
let mut it = v.iter();
println!("{:?}", it.next());
println!("{:?}", it.next());
println!("{:?}", it.next());
println!("{:?}", it.next());
}Some(10)
Some(20)
Some(30)
None
v.iter() builds an iterator over the vector; it must be mut because each next mutates its internal position. Three Somes, then None forever after. A for loop is exactly this: call next in a loop, bind each Some value to the loop variable, stop at the first None. We'll prove that claim properly in lesson 19.6; for now, know that the loops you've written were calling next all along.
Three ways to iterate a collection
That little program called v.iter(). There are three sibling methods, and the difference between them is, once again, just chapter 8 and 9 ownership applied to iteration. Each produces items of a different type:
iter() yields shared references &T. The collection is borrowed; you can read each element but not change it, and the collection is still yours afterward.
iter_mut() yields mutable references &mut T. The collection is mutably borrowed; you can change each element in place.
into_iter() yields owned values T. The collection is consumed (moved into the iterator); you get each element by value, and the original is gone.
Here are all three:
fn main() {
let v = vec![1, 2, 3];
let total: i32 = v.iter().sum(); // &i32 each; v still usable
println!("sum is {total}, v is {v:?}");
let mut v2 = vec![1, 2, 3];
for n in v2.iter_mut() { // &mut i32 each
*n *= 10;
}
println!("{v2:?}");
let v3 = vec![String::from("a"), String::from("b")];
for s in v3.into_iter() { // String each, by value; v3 consumed
println!("owning {s}");
}
// v3 is gone here.
}sum is 6, v is [1, 2, 3]
[10, 20, 30]
owning a
owning b
This is why for x in &v and for x in v behave differently, and you've felt it without naming it. &v calls iter() (borrow, x is &T), &mut v calls iter_mut(), and a bare v calls into_iter() (consume, x is T). Choosing the loop header chooses the ownership mode. The owned form is the one you want when you need to take the elements out, for example to move each String somewhere else; the borrowing forms are the everyday default.
Key insight
iter / iter_mut / into_iter are the iteration version of & / &mut / owned. Whenever you reach for an iterator, the first decision is the same one you make for any value: do I need to read the elements, modify them in place, or take ownership of them? That answer picks the method, and everything downstream, what the loop variable's type is, whether the collection survives, follows from it.
Iterators are lazy
Here is the single most important fact about iterators, and the one beginners trip on. An iterator does nothing until something asks it for values. Building one is free and instant; it just sets up the machine. Look:
fn main() {
let v = vec![1, 2, 3];
let doubler = v.iter().map(|x| {
println!("doubling {x}");
x * 2
});
println!("iterator built, nothing has happened yet");
}iterator built, nothing has happened yet
The map closure, which prints "doubling", never runs. map (the subject of the next lesson) doesn't double anything; it builds a new iterator that will double, when asked. Nothing asks here, so nothing happens. This is called laziness, and it's deliberate: it lets you chain many transformations together with no wasted work, because each value flows through the whole chain only when it's finally pulled out the end.
What pulls values out? A for loop does, or a method that consumes the iterator, like sum, count, or collect (lesson 19.5). Add one and the work happens:
fn main() {
let v = vec![1, 2, 3];
let doubled: Vec<i32> = v.iter().map(|x| {
println!("doubling {x}");
x * 2
}).collect();
println!("{doubled:?}");
}doubling 1
doubling 2
doubling 3
[2, 4, 6]
Now collect drives the iterator, repeatedly calling next, which pulls each value through map, which runs the closure. Laziness is why "build a pipeline, then run it once at the end" is the natural shape of iterator code, and why an iterator with no consumer is almost always a bug the compiler will warn you about (unused must-use on the iterator).
A built-but-unconsumed iterator does nothing
v.iter().map(|x| do_something(x)); with no consumer at the end runs do_something zero times. New Rust programmers write this expecting a side effect and get silence. If you want to run a closure for its effect over every element and don't need a result, either use a for loop (clearer for side effects) or end the chain with .for_each(...). A lone adapter chain that you drop on the floor is dead code, and the compiler warns you so.
Quiz time
Question #1
What is the one required method of the Iterator trait, and how does an iterator signal that it's finished?
Show solution
The required method is next(&mut self) -> Option<Self::Item>. It returns Some(item) for each value in turn and None once the sequence is exhausted. The None is the "finished" signal; there's no separate query. Item is an associated type naming what the iterator yields. (next takes &mut self because advancing changes the iterator's internal position.)
Question #2
You have let v = vec![String::from("a"), String::from("b")]. What's the difference between v.iter(), v.iter_mut(), and v.into_iter()?
Show solution
v.iter() yields &String (shared borrow, read-only, v survives). v.iter_mut() yields &mut String (mutable borrow, lets you change elements in place, v survives). v.into_iter() yields String by value (consumes v, moving each element out; v is gone afterward). They're the & / &mut / owned distinction from chapters 8 and 9, applied to iteration.
Question #3
What does this print, and why?
let nums = vec![1, 2, 3];
nums.iter().map(|x| println!("{x}"));
println!("done");Show solution
It prints just done. The map builds a lazy iterator but nothing consumes it, so the closure never runs and none of the numbers print. (The compiler also warns that the iterator is unused.) To actually print each number, use a for loop or end with .for_each(|x| println!("{x}")).
You now know what an iterator is and that it does nothing until driven. The next lesson (19.4) is the fun part: the adapters like map, filter, and zip that transform one iterator into another, building the pipelines laziness was designed for.