RustArena

Step 7 of 7

Implementing Iterator for your own type

Any type can be an iterator: implement next, and you get every adapter and consumer (map, filter, take, sum, collect and about 70 more) for free, because they're default methods of the Iterator trait.

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<Self::Item> {
        let value = self.current;
        self.current = self.next;
        self.next += value;
        Some(value)
    }
}

fn main() {
    let first: Vec<u64> = Fibonacci::new().take(10).collect();
    println!("{:?}", first);

    let even_sum: u64 = Fibonacci::new()
        .take_while(|&f| f < 4_000_000)
        .filter(|f| f % 2 == 0)
        .sum();
    println!("{even_sum}");
}
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
4613732

Piece by piece:

Returning iterators from functions

Often you don't need a new struct: return a chain built from existing iterators, typed as impl Iterator<Item = ...>. Callers decide what to do with it (count it, take a few, collect it), and nothing is allocated in between:

struct Team {
    members: Vec<(String, bool)>, // (name, active?)
}

impl Team {
    fn active(&self) -> impl Iterator<Item = &str> {
        self.members.iter().filter(|m| m.1).map(|m| m.0.as_str())
    }
}

fn main() {
    let team = Team {
        members: vec![
            (String::from("ann"), true),
            (String::from("bo"), false),
            (String::from("cy"), true),
        ],
    };
    println!("{}", team.active().count());
    let names: Vec<&str> = team.active().collect();
    println!("{}", names.join(", "));
}
2
ann, cy

This is a common pattern in libraries: return impl Iterator instead of a Vec when the caller might not need every item.

Common mistakes

struct Countdown {
    remaining: u32,
}

impl Iterator for Countdown {
    fn next(&mut self) -> Option<u32> {
        None
    }
}

fn main() {
    let c = Countdown { remaining: 3 };
    println!("{}", c.remaining);
}
error[E0046]: not all trait items implemented, missing: `Item`

Your task

Implement Iterator for Countdown. Countdown::new(3) yields 3, 2, 1 and then None (and keeps returning None after that). Countdown::new(0) yields nothing.

Challenge 2

The Collatz sequence starts at a number n: if n is even the next number is n / 2, if it's odd the next is 3 * n + 1, and it ends when it reaches 1. Implement Iterator for Collatz so that Collatz::new(6) yields 6, 3, 10, 5, 16, 8, 4, 2, 1 and then None. The done field remembers that 1 has been yielded. (The start is always at least 1.)

Challenge 3

APIs often return results a page at a time. Implement Iterator for Pages, which walks a slice of ids in pages of size items: with ids [1, 2, 3, 4, 5] and size 2, it yields [1, 2], [3, 4], [5], then None. Each item is a sub-slice of the original (&'a [i32]), so nothing is copied. (size is always at least 1.)

You'll need split_at from the slices module, and min so the last page can be shorter.

Previous: collect, the turbofish, and collecting Results