Implementing Your Own Iterator
Iterators Over Loops covered the consumer side: chaining .map, .filter, .collect() on iterators the standard library already gives you. This tutorial covers the producer side, implementing Iterator for your own type, which is what makes every one of those adapters available on it for free, without writing a single one yourself.
The Minimal Iterator Implementation
Iterator has exactly one required method: next, returning Some(item) while there's more to produce, and None once exhausted. Everything else, .map, .filter, .zip, .take, dozens of methods, is a default method implemented in terms of next.
struct Fibonacci {
curr: u64,
next: u64,
}
impl Fibonacci {
fn new() -> Self {
Fibonacci { curr: 0, next: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let result = self.curr;
let new_next = self.curr + self.next;
self.curr = self.next;
self.next = new_next;
Some(result)
}
}
That single next implementation is enough to use every adapter the standard library provides:
let first_ten: Vec<u64> = Fibonacci::new().take(10).collect();
let even_fibs: Vec<u64> = Fibonacci::new().take(20).filter(|n| n % 2 == 0).collect();
Note this Fibonacci iterator never returns None, it's infinite. That's fine, and common, as long as something downstream bounds it, here .take(10) is what stops it. An infinite iterator with no .take(), .find(), or similar bound will simply run forever, the same way an unbounded loop would.
IntoIterator: Making for x in my_thing Work
for x in collection doesn't call .next() directly, it desugars to calling .into_iter() on collection first. IntoIterator is the trait that makes a type usable in a for loop, and the standard library implements it three different ways for most collections, depending on whether the loop should consume, borrow, or mutably borrow.
struct Stack<T> {
items: Vec<T>,
}
// consumes the Stack, yields owned T
impl<T> IntoIterator for Stack<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
}
}
// borrows the Stack, yields &T
impl<'a, T> IntoIterator for &'a Stack<T> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
}
}
let stack = Stack { items: vec![1, 2, 3] };
for item in &stack {
println!("{item}"); // borrows: &i32, stack still usable afterward
}
for item in stack {
println!("{item}"); // consumes: i32, stack is gone after this loop
}
This mirrors exactly what Vec<T> itself does: for x in &vec borrows, for x in vec consumes. Implementing both on your own collection gives callers that same, familiar choice instead of forcing them through an explicit .iter() call every time. Most of the actual work above is delegation, Vec's own IntoIter/Iter types do the heavy lifting; your impl just hands off to them.
size_hint: Letting Adapters Pre-Allocate
size_hint is an optional method returning (usize, Option<usize>), a lower bound and an optional upper bound on how many items remain. .collect() and similar methods use it to pre-allocate the right capacity instead of growing incrementally.
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<u32> { /* ... */ }
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining as usize, Some(self.remaining as usize))
}
}
If you know the exact count, also implement ExactSizeIterator, a marker trait that asserts size_hint's lower and upper bounds are equal, which unlocks .len() directly on the iterator. Skipping size_hint entirely is fine, it defaults to (0, None), "no information", collect() still works correctly, just without the pre-allocation benefit.
DoubleEndedIterator: Enabling .rev()
If your iterator can produce items from either end, implement next_back alongside next via DoubleEndedIterator, which is what makes .rev() available:
impl DoubleEndedIterator for Countdown {
fn next_back(&mut self) -> Option<u32> {
// produce the item that would otherwise come last
}
}
Not every iterator can support this meaningfully, the Fibonacci example above has no defined "last" element to start from, so it has no DoubleEndedIterator implementation. Only implement it when "iterate from the other end" is a coherent operation for the type.
A Well-Behaved Iterator Keeps Returning None
Once next returns None, callers expect every subsequent call to also return None, not flip back to producing items. Adapters like .chain() and .zip() rely on this: they call next past the first None to check whether the other iterator they're combined with is also exhausted.
// BAD: this iterator can return Some again after a None
impl Iterator for Flaky {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.toggle { Some(self.value) } else { None }
// self.toggle flips elsewhere, so None isn't actually final
}
}
If you're wrapping a source that genuinely can behave this way (some external API that intermittently returns nothing), wrap it in .fuse(), a standard library adapter that remembers the first None and returns it forever after, rather than relying on every downstream consumer to handle a resurrected iterator correctly.
Key Takeaways
- Implementing
Iteratorrequires onlytype Itemandfn next(&mut self) -> Option<Self::Item>. Every adapter (.map,.filter,.take, ...) is a default method built on top of that one function. - Implement
IntoIteratorforT,&T, and&mut Tseparately to supportfor x in collection,for x in &collection, andfor x in &mut collection, mirroring howVecitself behaves. size_hintlets.collect()and similar methods pre-allocate; implementExactSizeIteratoronly when the remaining count is genuinely exact.- Implement
DoubleEndedIterator'snext_backonly when "produce from the other end" is a coherent operation for the type, not every iterator has one. - A correct iterator returns
Noneforever once exhausted, neverSomeagain afterward. Wrap a source that can't guarantee that in.fuse().