The Iterator trait in Rust defines how to loop through a sequence of values. If a type implements Iterator, you can use it with for loops, .next(), and many functional-style methods like map, filter, and fold. Every iterator has a next() method that returns one item at a time.
When I started using iterators in Rust, I was surprised by how much power they gave me. Coming from JavaScript, I was used to array methods like map and filter, but Rust’s iterator system adds even more safety and control. Once I understood that iterators are just structs with a method that returns the next item, everything clicked into place.
In this post, we will explore the Iterator trait, how .next() works, how to build your own iterators, and how Rust handles iteration with safety and speed.
What Is the Iterator Trait?
The Iterator trait is a core part of Rust. Any type that implements it can be used in a for loop or with iterator methods like map, filter, and fold.
At the center of this trait is one method:
fn next(&mut self) -> Option<Self::Item>;
This means every iterator returns:
- Some(value) when there is another item
- None when the sequence is done
We already saw how iterators work when we covered Using Closures and for loops in Rust. Now we will dig deeper.
Built-in Iterators
Vectors, arrays, ranges, and many other types come with iterators:
let nums = vec![10, 20, 30];
let mut iter = nums.iter();
println!("{:?}", iter.next()); // Some(&10)
println!("{:?}", iter.next()); // Some(&20)
println!("{:?}", iter.next()); // Some(&30)
println!("{:?}", iter.next()); // None
The iter() method gives you a shared reference to the items.
If you want ownership of the items, use into_iter():
let mut owned = nums.into_iter(); // Now nums is moved
Or use iter_mut() to get mutable references.
Using .next() Manually
Even though you usually use for loops, you can call .next() yourself:
let data = vec![1, 2, 3];
let mut it = data.iter();
while let Some(x) = it.next() {
println!("{}", x);
}
This gives you full control, like a low-level loop.
for Loops Are Just Syntactic Sugar
This loop:
for n in vec![1, 2, 3] {
println!("{}", n);
}
Is the same as:
let mut iter = vec![1, 2, 3].into_iter();
while let Some(n) = iter.next() {
println!("{}", n);
}
Rust rewrites for loops under the hood using .next().
Creating a Custom Iterator
You can create your own struct and make it behave like an iterator.
Step 1: Define the struct:
struct Counter {
count: u32,
}
Step 2: Implement Iterator:
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
Step 3: Use it:
let mut c = Counter { count: 0 };
while let Some(n) = c.next() {
println!("{}", n);
}
This prints 1 to 5, one per line.
Why Use Custom Iterators?
- You can make complex data structures iterable
- You can control iteration logic
- You can use all of Rust’s iterator adapters with your custom type
This makes your code composable, readable, and safe.
Iterator vs IntoIterator
- Iterator trait defines how to move through data
- IntoIterator lets a collection produce an iterator
You usually implement Iterator for your iterator type, and IntoIterator for the container.
Most built-in types already implement both.
Summary
The Iterator trait powers much of Rust’s looping and functional style. At its core is the next() method, which returns one value at a time. Whether you use it with built-in types, write your own iterator, or chain functional methods like map, Rust iterators give you precise control and zero-cost abstraction.