In this lesson, you will learn how to design custom iterators that safely reference the data inside a collection without taking ownership of it.
The Problem of Ownership
When you write a custom iterator in Rust, you have a choice: do you consume the collection (taking ownership) or do you borrow it? If you want to iterate over a large vector multiple times without moving the data out, you must use a borrowed iterator.
To achieve this, your iterator struct must hold a reference to the collection. This immediately introduces a lifetime requirement: the iterator cannot outlive the collection it is borrowing from.
Defining the Borrowed Iterator
Let’s look at a custom iterator that borrows a slice:
struct MyIter<'a, T> {
data: &'a [T],
index: usize,
}
impl<'a, T> Iterator for MyIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.data.len() {
let item = &self.data[self.index];
self.index += 1;
Some(item)
} else {
None
}
}
}
The Mechanics at Play
- Lifetime Annotations (
'a): Because the iterator stores a reference (&'a [T]), we must explicitly tell the compiler that the data lives at least as long as the iterator itself. The'aannotation ties the iterator’s existence to the lifetime of the slice it traverses. - The
ItemAssociated Type: Notice thattype Item = &'a T. This signals that the iterator yields references. These references are "bound" by the same lifetime'a, ensuring the compiler prevents you from using the yielded items after the original collection has been dropped. - State Management: The struct holds a reference to the data and a cursor (
index). Because we only hold a shared reference, we cannot mutate the parent collection, which is exactly the behavior expected of standard immutable iterators likeiter().
By carefully defining these lifetime bounds, you ensure that the iterator remains a window into your data rather than a thief of your memory.
Key Takeaway: A borrowed iterator must store a reference with a lifetime annotation that links the iterator's lifespan directly to the parent collection, ensuring the iterator never outlives the data it traverses.