13.3 Creating Custom Iterators
While standard library iterators cover many use cases, you’ll often need to make your own data structures iterable. When creating custom iterators, there are generally two structural approaches:
- The Type is the Iterator: For simple cases, the type itself can hold the necessary iteration state (like a current index or value) and directly implement the
Iterator
trait, including thenext()
method. Instances of this type can then be used directly in loops or iterator chains. We will see this pattern with aCounter
example. - The Type Produces an Iterator: More commonly, especially for types acting as collections, the type itself doesn’t implement
Iterator
. Instead, it implements theIntoIterator
trait. Itsinto_iter()
method constructs and returns a separate iterator struct (which holds the iteration state and implementsIterator
with thenext()
logic). This is the pattern used by standard collections likeVec
and the one we’ll initially demonstrate for a customPixel
struct.
A key benefit of implementing the Iterator
trait (either directly on your type or on a separate iterator struct) is that you automatically gain access to a wide array of powerful adapter and consumer methods defined directly on the trait itself (like map
, filter
, fold
, sum
, collect
, and many others shown in Section 13.2). These methods have default implementations written in terms of the required next()
method. Therefore, by simply providing the core next()
logic for your specific type, you enable users to immediately leverage the entire rich ecosystem of standard iterator operations on your custom iterator, just like they would with standard library iterators.
Let’s illustrate these approaches with examples.
13.3.1 Example 1: Iterating Over Struct Fields (Manual Implementation)
This approach follows the second pattern mentioned above: the Pixel
struct implements IntoIterator
to produce separate iterator structs (PixelIter
, PixelIterMut
, etc.) which implement Iterator
. This is general but can involve boilerplate code.
#[derive(Debug, Clone, Copy)] // Added derives for easier use later struct Pixel { r: u8, g: u8, b: u8, } // --- Consuming Iterator (Yields owned u8) --- struct PixelIntoIterator { pixel: Pixel, // Owns the pixel data index: u8, // State: which component is next (0=r, 1=g, 2=b) } impl Iterator for PixelIntoIterator { type Item = u8; // Yields owned u8 values fn next(&mut self) -> Option<Self::Item> { let result = match self.index { 0 => Some(self.pixel.r), 1 => Some(self.pixel.g), 2 => Some(self.pixel.b), _ => None, // Sequence exhausted }; self.index = self.index.wrapping_add(1); // Use wrapping_add for safety result } } // Implement IntoIterator for Pixel to enable `for val in pixel` impl IntoIterator for Pixel { type Item = u8; type IntoIter = PixelIntoIterator; fn into_iter(self) -> Self::IntoIter { PixelIntoIterator { pixel: self, index: 0 } } } // --- Immutable Reference Iterator (Yields &u8) --- // Lifetime 'a ensures the iterator doesn't outlive the borrowed Pixel struct PixelIter<'a> { pixel: &'a Pixel, // Holds an immutable reference index: u8, } impl<'a> Iterator for PixelIter<'a> { type Item = &'a u8; // Yields immutable references fn next(&mut self) -> Option<Self::Item> { let result = match self.index { 0 => Some(&self.pixel.r), 1 => Some(&self.pixel.g), 2 => Some(&self.pixel.b), _ => None, }; self.index = self.index.wrapping_add(1); result } } // Implement IntoIterator for &Pixel to enable `for val_ref in &pixel` impl<'a> IntoIterator for &'a Pixel { type Item = &'a u8; type IntoIter = PixelIter<'a>; fn into_iter(self) -> Self::IntoIter { PixelIter { pixel: self, index: 0 } } } // --- Mutable Reference Iterator (Yields &mut u8) --- struct PixelIterMut<'a> { pixel: &'a mut Pixel, // Holds a mutable reference index: u8, } impl<'a> Iterator for PixelIterMut<'a> { type Item = &'a mut u8; // Yields mutable references // Returning mutable references from `next` when iterating over mutable // fields of a struct borrowed mutably can be tricky for the borrow checker. // Using raw pointers temporarily inside `next` is one pattern to handle this, // though it requires `unsafe`. It bypasses the borrow checker's static // analysis for this specific, localized operation, relying on the programmer // to ensure safety (which holds here as we access distinct fields per index). fn next(&mut self) -> Option<Self::Item> { let pixel_ptr: *mut Pixel = self.pixel; // Get raw pointer to the mutable pixel let result = match self.index { // Safety: `pixel_ptr` is valid, and index ensures we access distinct fields // mutably within the lifetime 'a. 0 => Some(unsafe { &mut (*pixel_ptr).r }), 1 => Some(unsafe { &mut (*pixel_ptr).g }), 2 => Some(unsafe { &mut (*pixel_ptr).b }), _ => None, }; self.index = self.index.wrapping_add(1); result } } // Implement IntoIterator for &mut Pixel to enable `for val_mut in &mut pixel` impl<'a> IntoIterator for &'a mut Pixel { type Item = &'a mut u8; type IntoIter = PixelIterMut<'a>; fn into_iter(self) -> Self::IntoIter { PixelIterMut { pixel: self, index: 0 } } } // Optional: Add convenience methods like standard collections impl Pixel { fn iter(&self) -> PixelIter<'_> { self.into_iter() // Equivalent to (&*self).into_iter() } fn iter_mut(&mut self) -> PixelIterMut<'_> { self.into_iter() // Equivalent to (&mut *self).into_iter() } } fn main() { let pixel1 = Pixel { r: 255, g: 0, b: 128 }; println!("Iterating by value (consumes pixel1):"); // Note: pixel1 cannot be used after this loop because it's moved for val in pixel1 { println!(" - Value: {}", val); } // println!("{:?}", pixel1); // Error: use of moved value let pixel2 = Pixel { r: 10, g: 20, b: 30 }; println!("\nIterating by immutable reference:"); for val_ref in pixel2.iter() { // or `for val_ref in &pixel2` println!(" - Ref: {}", val_ref); // *val_ref is u8 } println!("Pixel 2 after iter: {:?}", pixel2); // pixel2 is still usable let mut pixel3 = Pixel { r: 100, g: 150, b: 200 }; println!("\nIterating by mutable reference:"); for val_mut in pixel3.iter_mut() { // or `for val_mut in &mut pixel3` *val_mut = val_mut.saturating_add(10); // Modify value safely println!(" - Mut Ref: {}", *val_mut); } println!("Pixel 3 after iter_mut: {:?}", pixel3); let pixel4 = Pixel { r: 2, g: 3, b: 4 }; // Using methods inherited from the Iterator trait: let sum: u16 = pixel4.iter().map(|&v| v as u16).sum(); println!("\nSum using iter(): {}", sum); // Output: 9 let product: u32 = pixel4.into_iter().map(|v| v as u32).product(); println!("Product using into_iter(): {}", product); // Output: 24 // pixel4 is consumed here }
Key points from this example:
- Separate iterator structs (
PixelIntoIterator
,PixelIter
,PixelIterMut
) manage state and hold either owned data, an immutable reference, or a mutable reference. - Implementing
IntoIterator
forPixel
,&Pixel
, and&mut Pixel
makes the struct work seamlessly withfor
loops in all three modes. - Lifetimes (
'a
) are crucial for the reference iterators. - The
unsafe
block inPixelIterMut::next
demonstrates a pattern sometimes needed to safely return mutable references to different fields across calls, bypassing borrow checker limitations within the method body. - Crucially, even though we only implemented
next()
, we could still call.map()
and.sum()
or.product()
because those methods are provided by theIterator
trait itself.
13.3.2 Example 2: A Simple Self-Contained Iterator (Counter
)
Sometimes, the iterator is the primary object, holding its own state directly, rather than iterating over a separate collection. This follows the first structural pattern mentioned earlier: the type implements Iterator
directly.
// An iterator that counts from 'start' up to 'end' (inclusive). struct Counter { current: u32, end: u32, } impl Counter { fn new(start: u32, end: u32) -> Self { Counter { current: start, end } } } impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { if self.current <= self.end { let value = self.current; // Use saturating_add for safety against overflow, though unlikely here self.current = self.current.saturating_add(1); Some(value) } else { None // Signal the end of the iteration } } } fn main() { println!("Counting from 1 to 5:"); // The Counter struct itself implements Iterator let counter1 = Counter::new(1, 5); for count in counter1 { // `for` loop works directly on an Iterator println!(" - {}", count); } // Iterator methods like `sum` can be called directly on Counter // because it implements Iterator. let sum_of_range: u32 = Counter::new(10, 15).sum(); println!("\nSum of range 10 to 15: {}", sum_of_range); // 10+..+15 = 75 let mut counter2 = Counter::new(1, 3); assert_eq!(counter2.next(), Some(1)); assert_eq!(counter2.next(), Some(2)); assert_eq!(counter2.next(), Some(3)); assert_eq!(counter2.next(), None); assert_eq!(counter2.next(), None); // Should remain None (FusedIterator behavior) }
In this Counter
example, we didn’t need IntoIterator
. Why?
- The
Counter
struct itself implements theIterator
trait. It holds its own state (current
,end
). - The
for
loop and methods likesum()
are designed to work with any type that implementsIterator
. If the type passed to them already is an iterator (like ourcounter1
variable), they use it directly. - If, however, the type used in a
for
loop (like aVec
or ourPixel
struct) is not an iterator itself, the loop requires that type to implement theIntoIterator
trait. The loop then implicitly calls the.into_iter()
method on that type to obtain the actual iterator it needs. - Therefore,
IntoIterator
is primarily needed to define how to get an iterator from another type (like a collection). SinceCounter
is already the iterator, this step isn’t required for it.
13.3.3 Leveraging Array Iterators via Delegation
The manual implementation for Pixel
in the first example works, but involves significant boilerplate code. If the data within your struct can be logically represented as a standard collection type, like an array or a slice, you can often simplify the implementation significantly by delegating to the standard library’s existing, optimized iterators.
This approach involves:
- Storing the data internally in a standard collection (like an array).
- Implementing
IntoIterator
for your type (and its references) by calling the correspondinginto_iter()
,.iter()
, or.iter_mut()
methods on the internal collection.
Let’s revise the Pixel
struct to hold its components in an internal array [u8; 3]
and see how this simplifies the iterator implementations.
use std::slice::{Iter, IterMut}; // Import slice iterators for type annotations #[derive(Debug, Clone, Copy)] struct PixelArray { // Store components in an array channels: [u8; 3], // [r, g, b] } impl PixelArray { fn new(r: u8, g: u8, b: u8) -> Self { PixelArray { channels: [r, g, b] } } // Convenience accessors (optional but helpful) fn r(&self) -> u8 { self.channels[0] } fn g(&self) -> u8 { self.channels[1] } fn b(&self) -> u8 { self.channels[2] } } // Implement IntoIterator for PixelArray (consuming iteration) // This delegates to the array's consuming iterator. impl IntoIterator for PixelArray { type Item = u8; // Delegate to the array's consuming iterator type: `std::array::IntoIter` type IntoIter = std::array::IntoIter<u8, 3>; fn into_iter(self) -> Self::IntoIter { // Arrays implement IntoIterator, so we just call it on the internal array self.channels.into_iter() } } // --- We DO need explicit impl IntoIterator for &PixelArray --- // To enable `for item in &my_pixel_array`, we must implement `IntoIterator` // for the reference type `&PixelArray`. We achieve this easily by // delegating to the `.iter()` method of the internal `channels` array, // which returns an iterator yielding `&u8`. impl<'a> IntoIterator for &'a PixelArray { type Item = &'a u8; // The iterator type yielded by `.iter()` on an array/slice is `std::slice::Iter` type IntoIter = Iter<'a, u8>; fn into_iter(self) -> Self::IntoIter { // Call `.iter()` on the internal array self.channels.iter() } } // --- We DO need explicit impl IntoIterator for &mut PixelArray --- // Similarly, to enable `for item in &mut my_pixel_array`, we implement // `IntoIterator` for `&mut PixelArray`. This implementation delegates // to the internal array's `.iter_mut()` method, which returns an // iterator yielding `&mut u8`. impl<'a> IntoIterator for &'a mut PixelArray { type Item = &'a mut u8; // The type yielded by `.iter_mut()` on an array/slice is `std::slice::IterMut` type IntoIter = IterMut<'a, u8>; fn into_iter(self) -> Self::IntoIter { // Call `.iter_mut()` on the internal array self.channels.iter_mut() } } // By providing these implementations, we correctly leverage the standard // library's efficient slice iterators (`slice::Iter` and `slice::IterMut`) // for our custom type, without needing to rewrite the iteration logic itself. // Optional convenience methods (often added for discoverability, mirroring std lib) impl PixelArray { pub fn iter(&self) -> Iter<'_, u8> { self.channels.iter() // Delegate directly } pub fn iter_mut(&mut self) -> IterMut<'_, u8> { self.channels.iter_mut() // Delegate directly } } fn main() { let pixel = PixelArray::new(255, 0, 128); println!("Iterating by value (consuming):"); // `for val in pixel` calls `pixel.into_iter()` for val in pixel { println!(" - Value: {}", val); } // pixel is consumed here let pixel_ref = PixelArray::new(10, 20, 30); println!("\nIterating by immutable ref. (via impl IntoIterator for &PixelArray):"); // `for val_ref in &pixel_ref` now correctly calls `(&pixel_ref).into_iter()` for val_ref in &pixel_ref { // This now works println!(" - Ref: {}", val_ref); // val_ref is &u8 } // Example using the convenience method explicitly: // for val_ref in pixel_ref.iter() { println!(" - Ref: {}", val_ref); } let mut pixel_mut = PixelArray::new(100, 150, 200); println!("\nIterating by mutable ref. (via impl IntoIterator for &mut PixelArray):"); // `for val_mut in &mut pixel_mut` now correctly calls `(&mut pixel_mut).into_iter()` for val_mut in &mut pixel_mut { // This now works *val_mut = val_mut.saturating_sub(10); // Modify println!(" - Mut Ref: {}", *val_mut); // val_mut is &mut u8 } println!("Pixel after mut iteration: {:?}", pixel_mut); // Example using the convenience method explicitly: // for val_mut in pixel_mut.iter_mut() { *val_mut += 5; // println!(" - Mut Ref: {}", *val_mut); } // We can still use map, sum etc. because the iterators produced // (`std::array::IntoIter`, `slice::Iter`, `slice::IterMut`) implement Iterator. let pixel_sum = PixelArray::new(5, 6, 7); let sum: u16 = pixel_sum.iter().map(|&v| v as u16).sum(); println!("\nSum using iter() on PixelArray: {}", sum); // Output: 18 }
This section demonstrates how to make a struct iterable in all three modes by containing a standard collection (an array in this case) and implementing the necessary IntoIterator
traits via simple delegation. This is often much less work and less error-prone than implementing the next()
logic manually, while also benefiting from the performance of the standard library’s iterators.