Iterators
Lesson Overview
# Introduction
About
In Rust, an iterator is any value that implements the Iterator trait.
The values of an iterator can be manually advanced by calling the next method on the iterator.
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// many built-in methods implemented
}
Many of Rust’s collections implement
IntoIterator,
which can produce an Iterator from the collection’s values.
trait IntoIterator where Self::IntoIter: Iterator<Item=Self::Item> {
type Item;
type IntoIter: Iterator;
fn into_iter(self) -> Self::IntoIter;
}
Note that while IntoIterator::into_iter consumes its self type, that type does not necessarily have to be an owned value.
Thus, there are IntoIterator implementations for Vec,
for &'a Vec,
and for &'a mut Vec.
These implementations for varying degrees of ownership provide useful flexibility.
An understanding of traits is not necessary for this concept and we will have a whole exercise for you to master traits.
For now, just remember that:
- Any type that implements
IntoIteratoris called an iterable because it can be turned into an iterator. - Any type that implements
Iteratoris an iterator, which produces values and can be iterated over. - You can get an iterator from any type that implements
IntoIteratorby calling itsinto_itermethod. - Every iterator implements
IntoIteratorand produces itself. forloops always operate on iterators and callIntoIteratorimplicitly on their range argument.
into_iter and for loops
You’re probably familiar with for loops over a vector:
// lor is a `Vec`, which implements `IntoIterator`
let lor = vec!["Ainur", "Elves", "Men", "Dwarves", "Hobbits", "Orcs", "Dragons"];
for people in &lor {
println!("{people}");
}
Fundamentally, a for loop is just shorthand for calling its argument’s into_iter method,
and then calling next() until it stops producing items. In other words, the previous loop desugars as:
{
let mut iterator = (&lor).into_iter();
while let Some(people) = iterator.next() {
println!("{people}");
}
}
iter and iter_mut methods
Most Rust collection types provide iter and iter_mut methods.
For a collection of type T, these two methods perform the following by convention:
- The iterator returned by
iterwill yield immutably borrowed&T. - The iterator returned by
iter_mutwill yield mutably borrowed&mut T.
let mut v = vec![1, 2, 3];
// Using `iter` method
let mut iterator = v.iter();
assert_eq!(iterator.next(), Some(&1));
assert_eq!(iterator.next(), Some(&2));
assert_eq!(iterator.next(), Some(&3));
assert_eq!(iterator.next(), None);
// Using `iter_mut` method
for value in v.iter_mut() {
*value *= *value;
}
println!("{v:?}");
// => [1, 4, 9]
Difference between iter, iter_mut, and into_iter
If iter, iter_mut, and into_iter all produce iterators, what are the differences?
- The iterator returned by
iterwill conventionally yield immutably borrowed&T. - The iterator returned by
iter_mutwill conventionally yield mutably borrowed&mut T. - The iterator returned by
into_itermay yieldT,&Tor&mut T, depending on its context.
What is the context for an into_iter invocation?
Its self type.
Recall that there are IntoIterator implementations for Vec, &'a Vec, and &'a mut Vec.
Therefore:
(&v).into_iter()is equal tov.iter()=> returns&T(&mut v).into_iter()is equal tov.iter_mut()=> returns&mut Tv.into_iter()=> returnsTand moves the values
This is most clear in for loops:
for num in &v {...}=> binds&Tfor num in &mut v {...}=> binds&mut Tfor num in v {...}=> bindsTand moves the values
It is often clearer to write v.iter() instead of (&v).into_iter().
Therefore, there are ergonomic reasons for preferring iter and iter_mut over into_iter.
Using built-in methods for iterators
Iterators provide numerous built-in methods, such as map, filter, collect.
These methods are sometimes used with closures.
map uses a closure to transform the values of an iterator.
take truncates a (potentially infinite) iterator into one which terminates after at most the specified number of items.
for even_number in (0..).into_iter().map(|x| x * 2).take(4) {
let odd_number = even_number + 1;
println!("{even_number} => {odd_number}");
}
// 0 => 1
// 2 => 3
// 4 => 5
// 6 => 7
filter transforms the iterator into one which only returns those values for which the closure evaluates to true.
count consumes the iterator, returning the number of items produced.
let lor = vec!["Ainur", "Elves", "Men", "Dwarves", "Hobbits", "Orcs", "Dragons"];
let short_people = ["Dwarves", "Hobbits"];
let num_tall_people = lor.iter().filter(|people| !short_people.contains(people)).count();
assert_eq!(num_tall_people, 5);
flat_map transforms an iterator by transforming each item into a new iterator via the provided closure,
then flattening all the produced new iterators by chaining them together.
collect produces a new collection,
as long as the collection type implements FromIterator.
let words = ["alpha", "beta", "gamma"];
// chars() returns an iterator
let merged: String = words.iter()
.flat_map(|s| s.chars())
.collect();
assert_eq!(merged, "alphabetagamma");
The Iterator trait provides many such useful iterator transforms;
it’s worth reading the documentation and familiarizing yourself with them.
Laziness
It is important to know that Rust iterators are lazy: they produce no values until we consume the iterator. If you write and run the following code:
(0..5).map(|x| x * x);
You will get the following compiler error:
--> src/main.rs:2:5
|
2 | (0..5).map(|x| x * x);
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: iterators are lazy and do nothing unless consumed
This is because map is an adapter method that returns another iterator.
To consume an iterator, either use a for loop or use an iterator method which produces some value which is not itself an iterator.
For instance, collect is a consumer method (its function signature does not return an iterator but a generic type):
// Iterator Adaptor Consumer
// | | |
let v: Vec<_> = (0..5).map(|x| x * x).collect();
Originally from Exercism rust concepts