← Back to Rust Course | Chapter 14: Closures, Iterators & Async | Lesson 4 of 6

Building Custom Iterators

You can design your own kind of list-walker for your own data, teaching it exactly how to hand out items one at a time.

A Custom Iterator Struct

A custom iterator is typically a small struct holding whatever state is needed to compute the next item, with Iterator implemented on top of it.

Example: A Custom Iterator Struct

markup
struct Evens {
    current: i32,
    max: i32,
}

impl Iterator for Evens {
    type Item = i32;
    fn next(&mut self) -> Option<i32> {
        if self.current > self.max {
            None
        } else {
            let value = self.current;
            self.current += 2;
            Some(value)
        }
    }
}

fn main() {
    let evens = Evens { current: 0, max: 8 };
    for n in evens {
        println!("{}", n);
    }
}

Using Adapters on a Custom Iterator

Once Iterator is implemented, a custom type automatically supports the full range of adapter methods, letting you build pipelines just as you would on a built-in iterator.

Example: Using Adapters on a Custom Iterator

markup
struct Evens {
    current: i32,
    max: i32,
}

impl Iterator for Evens {
    type Item = i32;
    fn next(&mut self) -> Option<i32> {
        if self.current > self.max {
            None
        } else {
            let value = self.current;
            self.current += 2;
            Some(value)
        }
    }
}

fn main() {
    let total: i32 = Evens { current: 0, max: 10 }.map(|n| n * 2).sum();
    println!("Total: {}", total);
}

Implementing IntoIterator for a Collection

Implementing IntoIterator for a custom collection struct allows it to be used directly with a for loop, delegating to an inner iterator type.

Example: Implementing IntoIterator for a Collection

markup
struct Bag {
    items: Vec<i32>,
}

impl IntoIterator for Bag {
    type Item = i32;
    type IntoIter = std::vec::IntoIter<i32>;
    fn into_iter(self) -> Self::IntoIter {
        self.items.into_iter()
    }
}

fn main() {
    let bag = Bag { items: vec![10, 20, 30] };
    for item in bag {
        println!("{}", item);
    }
}

Ensuring the Iterator Terminates

A correctly implemented iterator must eventually return None; otherwise, any code that consumes it fully (like .collect() or .sum()) will loop forever.

Example: Ensuring the Iterator Terminates

markup
struct Countdown(u32);

impl Iterator for Countdown {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if self.0 == 0 {
            None
        } else {
            self.0 -= 1;
            Some(self.0 + 1)
        }
    }
}

fn main() {
    let collected: Vec<u32> = Countdown(5).collect();
    println!("{:?}", collected);
}
Common Mistakes
  1. Forgetting to implement IntoIterator for a collection struct if you want it to work directly in a for loop.
  2. Writing next() in a way that never returns None, causing any consuming method like .collect() to loop forever.
  3. Reimplementing common adapter logic manually instead of just implementing Iterator once and reusing the built-in adapter methods.
Chapter Summary
  • Implementing Iterator for a custom struct requires defining type Item and the next() method.
  • Implementing IntoIterator lets a custom collection type be used directly in a for loop.
  • A well-behaved iterator must eventually return None from next(), or consumers like .collect() will loop forever.
  • Custom iterators automatically gain access to the full standard library of iterator adapter methods.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.