Skip to content

Rust Custom Iterators

On the Iterators page you used iterators that the standard library gives you: vec.iter(), .map(), .filter(), .sum(). But you can also make your own iterators. A countdown timer, the Fibonacci numbers, the pages of a report, the lines of a custom file format — anything that hands out values one at a time can be an iterator.

The good news: you only write one method, next. Rust then gives you every adapter — map, filter, take, sum, zip, and dozens more — for free. Think of it like a ticket machine. You build the part that prints the next ticket. The queue, the display, and the “now serving” sign all work automatically.

To make a type an iterator, implement the Iterator trait. It needs two things:

  • type Item — what kind of value the iterator hands out (see Advanced Traits for more on associated types).
  • fn next(&mut self) -> Option<Self::Item> — return Some(value) for the next value, or None when there are no more.
struct Countdown {
current: u32,
}
impl Countdown {
fn new(from: u32) -> Self {
Countdown { current: from }
}
}
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.current == 0 {
None // finished
} else {
let value = self.current;
self.current -= 1;
Some(value)
}
}
}
fn main() {
let mut countdown = Countdown::new(3);
println!("{:?}", countdown.next());
println!("{:?}", countdown.next());
println!("{:?}", countdown.next());
println!("{:?}", countdown.next());
println!("{:?}", countdown.next());
for n in Countdown::new(3) {
println!("{}...", n);
}
println!("Liftoff!");
}

Output:

Some(3)
Some(2)
Some(1)
None
None
3...
2...
1...
Liftoff!
▶ Try it Yourself

A few things to notice:

  • next takes &mut self because it changes the iterator. The struct remembers where it is (current), and each call moves it forward. That’s why countdown must be let mut.
  • Returning None means “I’m done”. A for loop calls next again and again and stops at the first None.
  • Our countdown keeps returning None after it finishes. That’s polite, but not required by the trait — some iterators could start again. If you need the guarantee, call .fuse() on any iterator.

An iterator doesn’t have to end. The Fibonacci sequence (0, 1, 1, 2, 3, 5, 8, …) goes on forever: each number is the sum of the two before it. The iterator just needs to remember the last two numbers:

struct Fibonacci {
current: u64,
next: u64,
}
impl Fibonacci {
fn new() -> Self {
Fibonacci { current: 0, next: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let value = self.current;
// checked_add returns None on overflow, and `?` passes that None on,
// so the iterator ends instead of crashing when numbers get too big.
let new_next = self.current.checked_add(self.next)?;
self.current = self.next;
self.next = new_next;
Some(value)
}
}
fn main() {
let first_ten: Vec<u64> = Fibonacci::new().take(10).collect();
println!("{:?}", first_ten);
}

Output:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
▶ Try it Yourself

.take(10) is the key. Without it, collect() would try to collect the whole sequence. With an endless iterator, always limit it with something like take, take_while, or find.

Here’s the best part. The Iterator trait has around 75 methods, and all of them except next have default implementations built on top of next. So the moment you write next, your type can use all of them:

struct Countdown {
current: u32,
}
impl Countdown {
fn new(from: u32) -> Self {
Countdown { current: from }
}
}
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.current == 0 {
None
} else {
let value = self.current;
self.current -= 1;
Some(value)
}
}
}
fn main() {
let total: u32 = Countdown::new(5).sum();
println!("Sum: {}", total);
let evens: Vec<u32> = Countdown::new(10).filter(|n| n % 2 == 0).collect();
println!("Evens: {:?}", evens);
let labels: Vec<String> = Countdown::new(3).map(|n| format!("T-{}", n)).collect();
println!("Labels: {:?}", labels);
let multiple = Countdown::new(100).find(|n| n % 7 == 0);
println!("First multiple of 7: {:?}", multiple);
let pairs: Vec<(u32, char)> = Countdown::new(3).zip(['a', 'b', 'c']).collect();
println!("Zipped: {:?}", pairs);
}

Output:

Sum: 15
Evens: [10, 8, 6, 4, 2]
Labels: ["T-3", "T-2", "T-1"]
First multiple of 7: Some(98)
Zipped: [(3, 'a'), (2, 'b'), (1, 'c')]
▶ Try it Yourself

This is a great example of why Rust traits are so useful. One small method in, a huge toolbox out.

Adapters like map and take don’t do anything by themselves. They just build a “plan”. Work happens only when something consumes the iterator — a for loop, collect, sum, and so on. Let’s prove it with an iterator that prints every time next is called:

struct Noisy {
n: u32,
}
impl Iterator for Noisy {
type Item = u32;
fn next(&mut self) -> Option<u32> {
self.n += 1;
println!(" next() called, giving {}", self.n);
Some(self.n)
}
}
fn main() {
println!("Building the chain...");
let chain = Noisy { n: 0 }.map(|x| x * 10).take(3);
println!("Nothing has run yet. Now consuming:");
for value in chain {
println!("got {}", value);
}
println!("Done");
}

Output:

Building the chain...
Nothing has run yet. Now consuming:
next() called, giving 1
got 10
next() called, giving 2
got 20
next() called, giving 3
got 30
Done
▶ Try it Yourself

Two lessons here. First, building the chain printed nothing — no next() calls at all. Second, values flow through one at a time: one next, one map, one got, then the next value. There’s no hidden list in between. And Noisy never ends, yet the program finishes: after three items, take(3) simply stops asking.

So far our structs were iterators. Often, though, you have a collection — a struct that holds items — and you want people to loop over it, like they loop over a Vec. For that, you implement IntoIterator. It’s the trait a for loop uses behind the scenes: for x in thing really calls thing.into_iter().

Collections usually offer three ways to loop, and it helps to see them on your own type:

You write Method behind it Each item is Collection afterwards
for s in &playlist playlist.iter() &String (read only) still usable
for s in &mut playlist playlist.iter_mut() &mut String (can change) still usable
for s in playlist playlist.into_iter() String (owned) moved, gone

Here’s a Playlist that supports all three. Our struct keeps its songs in a Vec, so we can hand out the Vec’s own iterators instead of writing new ones:

struct Playlist {
songs: Vec<String>,
}
impl Playlist {
fn iter(&self) -> std::slice::Iter<'_, String> {
self.songs.iter()
}
fn iter_mut(&mut self) -> std::slice::IterMut<'_, String> {
self.songs.iter_mut()
}
}
// Makes `for song in &playlist` work
impl<'a> IntoIterator for &'a Playlist {
type Item = &'a String;
type IntoIter = std::slice::Iter<'a, String>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
// Makes `for song in &mut playlist` work
impl<'a> IntoIterator for &'a mut Playlist {
type Item = &'a mut String;
type IntoIter = std::slice::IterMut<'a, String>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
// Makes `for song in playlist` work (and uses up the playlist)
impl IntoIterator for Playlist {
type Item = String;
type IntoIter = std::vec::IntoIter<String>;
fn into_iter(self) -> Self::IntoIter {
self.songs.into_iter()
}
}
fn main() {
let mut playlist = Playlist {
songs: vec![String::from("intro"), String::from("main theme"), String::from("outro")],
};
for song in &playlist {
println!("Reading: {}", song);
}
for song in &mut playlist {
*song = song.to_uppercase();
}
println!("Longest: {:?}", playlist.iter().max_by_key(|s| s.len()));
for song in playlist {
println!("Taking: {}", song);
}
// `playlist` was moved by the last loop, so it can't be used here.
}

Output:

Reading: intro
Reading: main theme
Reading: outro
Longest: Some("MAIN THEME")
Taking: INTRO
Taking: MAIN THEME
Taking: OUTRO
▶ Try it Yourself

That’s a lot of lines, but each impl is the same shape: say what Item is, say what iterator type you return (IntoIter), and return it. The 'a is a lifetime: it says “the borrowed items live as long as the borrowed playlist”. You can copy this pattern for almost any collection that wraps a Vec.

Two extra traits are worth knowing about. You don’t need them often, but adapters like .rev() and .len() depend on them:

  • DoubleEndedIterator adds next_back, which takes items from the end. It unlocks .rev().
  • ExactSizeIterator promises the iterator knows exactly how many items are left. It unlocks .len(). You also give an accurate size_hint in Iterator.
// Yields low, low+1, ..., high-1 (like the range low..high)
struct Between {
low: u32,
high: u32,
}
impl Iterator for Between {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.low >= self.high {
return None;
}
let value = self.low;
self.low += 1;
Some(value)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let left = self.high.saturating_sub(self.low) as usize;
(left, Some(left))
}
}
impl DoubleEndedIterator for Between {
fn next_back(&mut self) -> Option<u32> {
if self.low >= self.high {
return None;
}
self.high -= 1;
Some(self.high)
}
}
impl ExactSizeIterator for Between {}
fn main() {
let backwards: Vec<u32> = Between { low: 1, high: 6 }.rev().collect();
println!("{:?}", backwards);
let mut both_ends = Between { low: 1, high: 6 };
println!("front {:?}, back {:?}", both_ends.next(), both_ends.next_back());
println!("{} left", both_ends.len());
}

Output:

[5, 4, 3, 2, 1]
front Some(1), back Some(5)
3 left
▶ Try it Yourself

The two ends move toward each other, and the iterator is finished when they meet. impl ExactSizeIterator for Between {} is empty: len() uses the size_hint you already wrote.

Writing a struct and an impl is great for iterators you reuse. For a quick one-off, the standard library has two helpers in std::iter:

  • iter::from_fn(closure) — calls your closure each time a value is needed. The closure returns Some(value) or None, just like next.
  • iter::successors(first, closure) — starts with a first value, and the closure computes each next value from the previous one. Returning None ends it.
use std::iter;
fn main() {
// from_fn: the closure keeps its own state (count)
let mut count = 3;
let countdown: Vec<u32> = iter::from_fn(|| {
if count == 0 {
None
} else {
count -= 1;
Some(count + 1)
}
})
.collect();
println!("{:?}", countdown);
// successors: each value is computed from the one before
let powers: Vec<u32> = iter::successors(Some(1u32), |&n| n.checked_mul(10)).collect();
println!("{:?}", powers);
// Fibonacci in one line: carry the last two numbers as a pair
let fib: Vec<u64> = iter::successors(Some((0u64, 1u64)), |&(a, b)| Some((b, a + b)))
.map(|(a, _)| a)
.take(10)
.collect();
println!("{:?}", fib);
}

Output:

[3, 2, 1]
[1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
▶ Try it Yourself

powers stops by itself: the next power of ten, 10,000,000,000, doesn’t fit in a u32, so checked_mul returns None and the iterator ends. A good rule: use these shortcuts inside one function, and write a named struct when other code needs to create the iterator, or when it has a lot of state.

Forgetting mut when calling next yourself. next changes the iterator, so the variable must be mutable:

let countdown = Countdown { current: 3 };
println!("{:?}", countdown.next());
error[E0596]: cannot borrow `countdown` as mutable, as it is not declared as mutable
--> src/main.rs:20:22
|
20 | println!("{:?}", countdown.next());
| ^^^^^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
19 | let mut countdown = Countdown { current: 3 };
| +++

Add mut. (A for loop doesn’t need it — it takes the iterator and handles that for you.)

Forgetting type Item. The trait needs to know what you hand out:

error[E0046]: not all trait items implemented, missing: `Item`
--> src/main.rs:5:1
|
5 | impl Iterator for Countdown {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `Item` in implementation
|
= help: implement the missing item: `type Item = /* Type */;`

Add type Item = u32; (or whatever your iterator yields) inside the impl.

Collecting an endless iterator. Fibonacci::new().collect::<Vec<_>>() (without take) compiles fine, but it never finishes in a useful way — it runs until the numbers overflow, or forever if your next never returns None. The compiler can’t catch this. When an iterator might be endless, always add take, take_while, or find before consuming it.

  • Implement Iterator with type Item and fn next(&mut self) -> Option<Self::Item>. Return None when you’re done.
  • Once next exists, you get map, filter, take, sum, zip, and dozens more for free.
  • Iterators are lazy: nothing happens until a for loop, collect, sum, etc. consumes them, and items flow through one at a time.
  • Implement IntoIterator for &MyType, &mut MyType, and MyType to support for loops that borrow, borrow mutably, or consume.
  • DoubleEndedIterator enables .rev(); ExactSizeIterator enables .len().
  • For quick one-off iterators, use std::iter::from_fn or std::iter::successors.

Quick check

1. What should next return when the iterator has no more items?

2. After implementing only next, can you call .map() and .sum() on your iterator?

3. In the lazy evaluation example, when is next() first called?

4. Which impl makes for song in &playlist work?

5. Which trait do you need so that .rev() works on your iterator?

Score: 0 / 5