In this lesson, you will learn how to create custom iterators that safely hold references to their parent data, ensuring that your data stays valid while you traverse it.
The Challenge of Internal State
When you build a custom iterator, you often need to store a reference to the collection you are iterating over. If you define a struct to hold this iterator, the compiler gets nervous: how can it be sure the collection won't be deleted while the iterator is still "walking" through it?
To solve this, we use lifetime bounds. We tell the compiler: "This iterator is tied to the lifetime of the data it references."
Defining the Bound
Imagine we have a Book and we want an iterator that yields its Chapters. If the iterator holds a reference to the Book, we must explicitly label that relationship.
struct ChapterIterator<'a> {
book: &'a Book,
current_index: usize,
}
impl<'a> Iterator for ChapterIterator<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
// We can safely reference self.book here
// because of the 'a lifetime bound.
self.book.get_chapter(self.current_index)
}
}
By adding <'a> to the struct and the impl block, we create a strict contract. The compiler now enforces that the Book must live at least as long as the ChapterIterator. If you tried to drop the Book while the ChapterIterator was still in scope, the compiler would stop you, preventing a "use-after-free" memory safety violation.
Why This Matters
Without these bounds, the compiler would assume the iterator is "independent" of its source data. If the source data were moved or destroyed, the iterator would be left pointing to garbage memory. By explicitly anchoring the iterator to the source via a lifetime parameter, we allow complex traversal logic to exist while keeping the ownership model completely deterministic and safe. You are essentially telling Rust: "This iterator is a transient view of this specific memory, and it cannot outlive its parent."
Key Takeaway: By attaching a lifetime parameter to your custom iterator struct, you formally link the iterator's existence to the data it traverses, guaranteeing memory safety across every step of your loop.