Wrapped Iterators in Rust

rust

Iā€™m pretty new to Rust, and one of the first things I immediately liked was Rust's iterators.

What I like about the iterators is how many operations are defined on every different type of iterator. For example, in JavaScript, there is the concept of an iterable, but the only thing that can be done on it is put it in a for..of loop. Things like map and filter are only defined on Array.

On the other hand, in Rust, map and filter are defined on any type that implements Iterator. Which means that any iterator that's added in the future gets all of these features for free. That's pretty neat.

How they did it ā€” that each function returns a different type that wraps the original iterator ā€” is a pretty clever solution. For example, if I have an iterator of type I, and I call .map(...) on it, the mapping code doesn't get run immediately. Instead, I immediately get a new object of type Map<I> back that is itself an iterator wrapping the original.

I wanted to see if I could do something similar. So as a contrived example, I want to add something like the enumerate but that works in a circle. So if "my string".chars() is an iterator that returns ['m', 'y', ' ', 's', 't', ...], then a circularly-enumerated iterator with a cycle length of 3 would return [(0, 'm'), (1, 'y'), (2, ' '), (0, 's'), (1, 't'), ...]. Let's give it a try.

// The structure that holds the wrapped iterator and the current state.
struct CircularEnumerate<I> {
    iter: I,
    items: u32,
    cur: u32,
}

// Implement the iterator.
impl<I> Iterator for CircularEnumerate<I> where I: Iterator {
    type Item = (u32, I::Item);
    
    fn next(&mut self) -> Option<(u32, I::Item)> {
        match self.iter.next() {
            // If the wrapped iterator is done, we're done.
            None => None,
            
            // If the wrapped iterator is not done, tack on
            // the circular enumeration.
            Some(v) => {
                let idx = self.cur;
                // Increment the current value, wrapping it
                // if necessary.
                self.cur = (self.cur + 1) % self.items;
                Some((idx, v))
            }
        }
    }
}

// For now, we need a way to create a new CircularEnumerator.
impl<I> CircularEnumerate<I> {
    fn new(iter: I, items: u32) -> CircularEnumerate<I> {
        CircularEnumerate { iter: iter, items: items, cur: 0 }
    }
}

fn main() {
    for (idx, i) in CircularEnumerate::new("abcdefghijkl".chars(), 3) {
        println!("{}: {}", idx, i);
    }
}

Edit on the Rust Playground

Well awesome. This prints out what we expect. And since this is an iterator, we can map, filter, count, take, fold, and collect it just like any other iterator.

In the Extending the Iterator Trait in Rust I look at how to integrate this new iterator into Rust in a natural way.