In this lesson, you will learn how to make your custom data structures "first-class citizens" in Rust’s functional pipeline by implementing the Iterator trait.
The Power of Laziness
Rust’s Iterator trait is the backbone of its functional programming style. When you create a custom collection, you don't just want to store data; you want the ability to chain .map(), .filter(), and .collect() calls. By implementing Iterator for your own type, you provide these capabilities without needing to allocate intermediate memory, thanks to Rust’s lazy evaluation.
To build this integration, you need to define two things: the Item type (what the iterator yields) and the next method.
Example: A Countdown Counter
Imagine a simple structure that tracks a countdown. By implementing Iterator, we allow this structure to behave like any other standard sequence:
struct Countdown {
current: u32,
}
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.current == 0 {
None
} else {
let val = self.current;
self.current -= 1;
Some(val)
}
}
}
Why This Matters
Once you define next, you inherit all the functional power of the Iterator trait for free (because of the default trait implementation of methods like map or filter that sit on top of next).
When a user calls my_counter.filter(|&x| x % 2 == 0).collect(), they are calling into your next method repeatedly. Because your type follows the iterator interface, the Rust compiler can optimize these chains into efficient machine code, often bypassing the need for temporary buffers entirely. You are essentially teaching your data structure to speak the language of Rust's most powerful functional abstractions.
Key Takeaway: Implementing the Iterator trait for your custom types enables seamless integration into functional pipelines, allowing you to leverage lazy evaluation and powerful iterator combinators with your own data structures.