Iterator
Trait
The Iterator
trait defines how an object can be used to produce a
sequence of values. For example, if we wanted to create an iterator that can
produce the elements of a slice it might look something like this:
struct SliceIter<'s> { slice: &'s [i32], i: usize, } impl<'s> Iterator for SliceIter<'s> { type Item = &'s i32; fn next(&mut self) -> Option<Self::Item> { if self.i == self.slice.len() { None } else { let next = &self.slice[self.i]; self.i += 1; Some(next) } } } fn main() { let slice = [2, 4, 6, 8].as_slice(); let iter = SliceIter { slice, i: 0 }; for elem in iter { println!("elem: {elem}"); } }
-
The
SliceIter
example implements the same logic as the C-stylefor
loop demonstrated on the last slide. -
Point out to the students that iterators are lazy: Creating the iterator just initializes the struct but does not otherwise do any work. No work happens until the
next
method is called. -
Iterators don't need to be finite! It's entirely valid to have an iterator that will produce values forever. For example, a half open range like
0..
will keep going until integer overflow occurs.
More to Explore
-
The "real" version of
SliceIter
is theslice::Iter
type in the standard library, however the real version uses pointers under the hood instead of an index in order to eliminate bounds checks. -
The
SliceIter
example is a good example of a struct that contains a reference and therefore uses lifetime annotations. -
You can also demonstrate adding a generic parameter to
SliceIter
to allow it to work with any kind of slice (not just&[i32]
).