An :idea[iterator] is ***a value that produces elements one by one***.
So far, to traverse a collection we've used the classic `for` loop.
```rust
let v = vec![1, 2, 3];
for x in &v {
println!("{}", x);
}This works great, but Rust offers us a much more powerful abstraction: Iterators.
An iterator is an object responsible for traversing a sequence of elements and determining when it’s done. In Rust, iterators are lazy, meaning they do absolutely nothing until you explicitly ask them to.
The Iterator trait
At the heart of it all is the Iterator trait. All iterators implement this contract. Its simplified definition looks like this:
pub trait Iterator {
type Item; // The type of data it returns
fn next(&mut self) -> Option<Self::Item>;
}All an object needs to be an iterator is to have a next method.
- Each time you call
next, the iterator returns the next element wrapped inSome(value). - When the data runs out, it returns
None.
We can use this manually (though we rarely will):
fn main() {
let v = vec![1, 2, 3];
// Create an explicit iterator
let mut iter = v.iter();
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None); // End of the sequence
}Why did the for loop work?
In reality, Rust’s for loop is “syntactic sugar”. When you write for x in v, Rust automatically calls .into_iter() and uses .next() repeatedly until it receives None.
In other words, the for loop is a consumer of iterators.
Types of iterators
Similar to what happened when traversing vectors, we have three ways to create an iterator depending on what we want to do with the data:
.iter(): Iterates over immutable references (&T)..iter_mut(): Iterates over mutable references (&mut T), allowing modification of the original collection..into_iter(): Creates an iterator that takes ownership (consumes) the collection and returns the values (T). The original collection is no longer valid.
Lazy evaluation
This is the key to understanding Rust’s performance.
Iterator adapters do not traverse the elements until a consumer forces them to. Building the chain creates some adapter values, usually very lightweight; the work on each element doesn’t start until it’s consumed.
Look at this code:
let v = vec![1, 2, 3];
// Nothing is executed here. Not a single calculation.
// We are only creating a structure that "knows" it has to add 1.
let transformed_iterator = v.iter().map(|x| x + 1);If we compile the code above, Rust will probably warn us that the iterator is unused. For something to happen, we need Consumer methods.
Consumer methods
These are methods that call next() internally and “consume” the iterator to produce a final result.
collect()
This is the most common one. It transforms the iterator back into a collection (like a Vec).
let v = vec![1, 2, 3];
// Now the map IS executed.
// We need to specify the type Vec<i32> because collect can create many things.
let v2: Vec<i32> = v.iter().map(|x| x + 1).collect();
println!("{:?}", v2); // [2, 3, 4]Other consumers
sum(): Sums all the elements.count(): Counts the elements.min()/max(): Finds the minimum or maximum.for_each(): Executes a closure for each element (side effects).
let total: i32 = v.iter().sum();