Course contentsShow
← Rust
Lesson 954 of 1,9108. Collections and Iterator PipelinesFree lesson

Custom Iterator Adapters

You will be able to build domain-specific iterators that encapsulate complex data generation logic.

In this lesson, you will learn how to encapsulate complex data generation logic into reusable, domain-specific iterator adapters that integrate seamlessly into Rust's functional pipelines.

The Power of Custom Adapters

While standard adapters like .map() or .filter() are powerful, they often lead to "logic clutter" when your business domain requires complex state transitions or data generation. By creating a custom struct that implements the Iterator trait, you can abstract away messy implementation details, making your iterator chains read like natural language.

Building a Domain-Specific Iterator

Suppose you are building a simulation that needs to generate a sequence of decaying values. Instead of writing a complex closure every time, you can define a custom adapter:

struct Decayer {
    current: f64,
    factor: f64,
}

impl Iterator for Decayer {
    type Item = f64;

    fn next(&mut self) -> Option<Self::Item> {
        self.current *= self.factor;
        if self.current < 0.01 { None } else { Some(self.current) }
    }
}

Now, you can turn this into a reusable adapter by creating a trait extension for any Iterator:

trait IteratorExt: Iterator + Sized {
    fn decay(self, factor: f64) -> Decayer {
        Decayer { current: 100.0, factor }
    }
}

impl<I: Iterator> IteratorExt for I {}

Why this matters

By wrapping logic in a dedicated struct, you gain three major advantages:

  1. Encapsulation: The internal state (like the current value and factor) is hidden from the consumer.
  2. Readability: Your calling code becomes highly expressive, such as my_data.decay(0.9).take(5).collect().
  3. Type Safety: You can enforce invariants during the next() call that would be difficult to manage in a simple map closure.

Custom adapters allow you to lift your code from simple data transformations to building a domain-specific "language" of data processing. Because Rust’s Iterator trait relies on lazy evaluation, your custom logic remains as efficient as hand-written loops, benefiting from zero-cost abstractions.

Key Takeaway: Custom iterator adapters allow you to hide complex state and generation logic behind clean, expressive method chains that compose perfectly with the rest of the Rust iterator ecosystem.