Nugget 14
HomeIntoIterator: The for Loop Interface
You write for x in vec and it works. But for x in &vec
also works. And for x in &mut vec works too. How does one syntax
produce three different behaviors? Meet IntoIterator.
IntoIterator Trait
This is the trait that powers every for loop:
trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter;
}
A type implements IntoIterator if it can be turned into an
iterator. The for loop is just sugar for:
// What you write:
for item in collection {
// ...
}
// What the compiler generates:
let mut iter = collection.into_iter();
while let Some(item) = iter.next() {
// ...
}
The key: it calls collection.into_iter(). So the behavior depends
entirely on which implementation of IntoIterator is used.
Vec<T> doesn't implement IntoIterator once. It
implements it three times — for Vec<T>,
&Vec<T>, and &mut Vec<T>:
// 1. for x in vec — consumes the Vec
impl<T> IntoIterator for Vec<T> {
type Item = T;
type IntoIter = IntoIter<T>;
fn into_iter(self) -> IntoIter<T> { /* ... */ }
}
// 2. for x in &vec — borrows immutably
impl<'a, T> IntoIterator for &'a Vec<T> {
type Item = &'a T;
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Iter<'a, T> { /* ... */ }
}
// 3. for x in &mut vec — borrows mutably
impl<'a, T> IntoIterator for &'a mut Vec<T> {
type Item = &'a mut T;
type IntoIter = IterMut<'a, T>;
fn into_iter(self) -> IterMut<'a, T> { /* ... */ }
}
That's why for x in &vec gives you &T items
without consuming vec — it's calling IntoIterator
for &Vec<T>, not Vec<T>.
for calls .into_iter()
This means for x in &vec is equivalent to
for x in vec.iter(). Both ultimately call .next()
on the same kind of iterator:
let v = vec![1, 2, 3];
// These are exactly equivalent:
for x in &v { println!("{x}"); }
for x in v.iter() { println!("{x}"); }
// And these are equivalent too:
for x in v {} // consumes v
for x in v.into_iter() {} // also consumes v
Here's how to implement IntoIterator for a custom collection:
struct MyList {
items: Vec<String>,
}
// Consuming iterator
impl IntoIterator for MyList {
type Item = String;
type IntoIter = std::vec::IntoIter<String>;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
}
}
// Borrowing iterator
impl<'a> IntoIterator for &'a MyList {
type Item = &'a String;
type IntoIter = std::slice::Iter<'a, String>;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
}
}
// Now you can write:
let list = MyList { items: vec!["a".into(), "b".into()] };
for item in &list { // borrows
println!("{item}");
}
for item in list { // consumes
println!("{item}");
}
// list is gone now
IntoIterator explains why everything works with for:
| Type | for x in type | Item type |
|---|---|---|
Vec<T> |
for x in vec |
T (consumes) |
&Vec<T> |
for x in &vec |
&T |
&mut Vec<T> |
for x in &mut vec |
&mut T |
[T; N] (array) |
for x in &arr |
&T |
Range<T> |
for x in 0..5 |
T |
HashMap<K, V> |
for x in &map |
(&K, &V) |
Option<T> |
for x in opt |
T (0 or 1 items) |
Everything that works with for implements IntoIterator.
It's a single trait that unifies all iteration.
IntoIterator is the trait behind every for loop — it converts a value into an iterator.Vec implements it three times (for owned, borrowed, mutably borrowed) enabling three loop behaviors.for x in &vec is equivalent to for x in vec.iter().IntoIterator on your own types to make them work with for loops.