- The iterator pattern allows you to perform some task on a sequence of items in turn.
- An iterator is responsible for the logic of iterating over each item and determining when the sequence has finished.
- In Rust, iterators are lazy, meaning they have no effect until you call methods that consume the iterator to use it up.
- Using an iterator in for loop:
let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { println!("Got: {}", val); }
- You cannot use
v1_iter
again afterfor
loop because the loop takes the ownership ofv1_iter
- You cannot use
- All iterators implement a trait named Iterator that is defined in the standard library.
- The definition of the trait looks like this:
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; // methods with default implementations elided }
- Implementing the
Iterator
trait requires that you also define anItem
type - The
Item
type will be the type returned from the iterator (vianext
method).
- Implementing the
- Example of
next
method:#[test] fn iterator_demonstration() { let v1 = vec![1, 2, 3]; let mut v1_iter = v1.iter(); assert_eq!(v1_iter.next(), Some(&1)); assert_eq!(v1_iter.next(), Some(&2)); assert_eq!(v1_iter.next(), Some(&3)); assert_eq!(v1_iter.next(), None); }
- Here, we needed to make
v1_iter
mutable: calling thenext
method on an iterator changes internal state that the iterator uses to keep track of where it is in the sequence. - In other words, this code consumes, or uses up, the iterator.
- Each call to next eats up an item from the iterator.
- We didn’t need to make
v1_iter
mutable when we used afor
loop because the loop took ownership ofv1_iter
and made it mutable behind the scenes.
- Here, we needed to make
- The
iter
method produces an iterator over immutable references.next
method above returns immutable references to the values stored inv1
(e.g.Some(&3)
)
into_iter
method takes ownership ofv1
and returns owned values.into_mut
method produces iterator over mutable references.
- Methods that call
next
are called consuming adaptors, because calling them uses up the iterator. - One example is the
sum
method, which takes ownership of the iterator and iterates through the items by repeatedly callingnext
, thus consuming the iterator. - As it iterates through, it adds each item to a running total and returns the total when iteration is complete.
- Example:
#[test] fn iterator_sum() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); let total: i32 = v1_iter.sum(); assert_eq!(total, 6); }
- We aren’t allowed to use
v1_iter
after the call tosum
becausesum
takes ownership of the iterator we call it on.
- We aren’t allowed to use
- Iterator adaptors are methods on
Iterator
trait that allows you to transform current iterator into a new one. - You can chain multiple calls to iterator adaptors to perform complex actions in a readable way.
- But because all iterators are lazy, you have to call one of the consuming adaptor methods to get results from calls to iterator adaptors.
- Using just the iterator adaptors would give a warning:
Warning:
let v1: Vec<i32> = vec![1, 2, 3]; v1.iter().map(|x| x + 1);
warning: unused `Map` that must be used --> src/main.rs:4:5 | 4 | v1.iter().map(|x| x + 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_must_use)]` on by default = note: iterators are lazy and do nothing unless consumed
- Consuming iterator adaptors:
let v1: Vec<i32> = vec![1, 2, 3]; let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); assert_eq!(v2, vec![2, 3, 4]);
- Here,
collect
methods consumes themap
iterator adaptor and creates a new vector[2, 3, 4]
- Here,
- Closures capture the environment:
struct Shoe { size: u32, style: String, } fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> { shoes.into_iter().filter(|s| s.size == shoe_size).collect() }
into_iter
takes ownership of vectorshoes
filter
adapts iterator into a new vector that allows only those elements for which the closure|s| s.size == shoe_size
returns true- This closure captures
shoe_size
from its environment. - Calling
collect
gathers the values returned by the adapted iterator into a vector that’s returned by the function.
- Struct
Counter
for which we want to create an iterator:struct Counter { count: u32, } impl Counter { fn new() -> Counter { Counter { count: 0 } } }
- Implementing
Iterator
forCounter
:impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } }
- We set the associated
Item
type for our iterator tou32
, meaning the iterator will returnu32
values.
- We set the associated
- Test for the above iterator:
#[test] fn calling_next_directly() { let mut counter = Counter::new(); assert_eq!(counter.next(), Some(1)); assert_eq!(counter.next(), Some(2)); assert_eq!(counter.next(), Some(3)); assert_eq!(counter.next(), Some(4)); assert_eq!(counter.next(), Some(5)); assert_eq!(counter.next(), None); assert_eq!(counter.next(), None); }
next
method starts withSome(1)
tillSome(5)
and finallyNone
- Calling
next
method subsequently will still returnNone
and not panic.
- We can now use other iterator methods. For example, if you want to
- create a pair of counter's current value and next value (e.g.
(1, 2)
), - take a product of the pair numbers (e.g.
1 * 2
), - keep those products which are multiple of 3 and then
- sum them you can use iterators to implement this:
#[test] fn using_other_iterator_trait_methods() { let sum: u32 = Counter::new() .zip(Counter::new().skip(1)) .map(|(a, b)| a * b) .filter(|x| x % 3 == 0) .sum(); assert_eq!(18, sum); }
- Note that
zip
returnsNone
if either of the pair isNone
, so(5, None)
is never produced.
- create a pair of counter's current value and next value (e.g.
- Iterators, although a high-level abstraction, get compiled down to roughly the same code as if you’d written the lower-level code yourself.
- Iterators are one of Rust’s zero-cost abstractions, by which we mean using the abstraction imposes no additional runtime overhead.
- In words of Bjarne Stroustrup (creator of C++):
In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.