Course contentsShow
← Rust
Lesson 722 of 1,9106. Generics and Static DispatchFree lesson

Bounds for Generic Iterators

Create custom iterator adapters that preserve type information for efficient chainable operations.

In this lesson, you will learn how to create custom iterator adapters that preserve the specific types of their inputs, enabling the compiler to optimize your code via static dispatch.

Preserving Type Information

When you write a generic function that takes an iterator, you might be tempted to use Box<dyn Iterator<Item = T>>. However, this forces "dynamic dispatch," where the compiler has to figure out the type at runtime—which is slower and prevents the compiler from inlining your logic.

Instead, we use generic bounds. By defining a struct that holds an iterator of a generic type I, we keep the concrete type visible to the compiler. This allows the compiler to perform "monomorphization," generating specialized, high-performance code for every type you pass in.

Creating a Custom Adapter

Let’s say we want to create an adapter that squares every number in an iterator.

struct SquareIterator<I> {
    iter: I,
}

impl<I, T> Iterator for SquareIterator<I>
where
    I: Iterator<Item = T>,
    T: std::ops::Mul<Output = T> + Copy,
{
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|x| x * x)
    }
}

In this example, SquareIterator<I> stores the original iterator. The where clause acts as our "bound." It tells the compiler: "I don't care what I is, as long as it iterates over a type T that supports multiplication."

Because we didn't hide I behind a pointer, the compiler knows exactly what the next() method does for a Vec<i32> versus a Range<i32>. It can inline the multiplication directly into the loop, resulting in machine code that is just as fast as if you had written the loop manually.

This pattern is the secret sauce behind map, filter, and chain in the standard library. By declaring your bounds clearly, you allow the compiler to treat your custom logic as a first-class extension of the language's native performance.

Key Takeaway: Use generic bounds on your struct types to maintain concrete type visibility, allowing the compiler to perform static dispatch and aggressive inlining for your custom iterator adapters.