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 Iterator requires only type Item and fn next(&mut self) -> Option<Self::Item>. Every adapter (.map, .filter, .take, ...) is a default method built on top of that one function.
  • Implement IntoIterator for T, &T, and &mut T separately to support for x in collection, for x in &collection, and for x in &mut collection, mirroring how Vec itself behaves.
  • size_hint lets .collect() and similar methods pre-allocate; implement ExactSizeIterator only when the remaining count is genuinely exact.
  • Implement DoubleEndedIterator's next_back only when "produce from the other end" is a coherent operation for the type, not every iterator has one.
  • A correct iterator returns None forever once exhausted, never Some again afterward. Wrap a source that can't guarantee that in .fuse().