Iterators
An iterator is a construct that can give you the items in the collection, one at a time. Actually, we have already used iterators a lot: the for
loop gives you an iterator. When you want to use an iterator other times, you have to choose what kind:
.iter()
for an iterator of references.iter_mut()
for an iterator of mutable references.into_iter()
for an iterator of values (not references)
A for
loop is actually just an iterator that owns its values. That's why it can make it mutable and then you can change the values when you use it.
We can use iterators like this:
fn main() { let vector1 = vec![1, 2, 3]; // we will use .iter() and .into_iter() on this one let vector1_a = vector1.iter().map(|x| x + 1).collect::<Vec<i32>>(); let vector1_b = vector1.into_iter().map(|x| x * 10).collect::<Vec<i32>>(); let mut vector2 = vec![10, 20, 30]; // we will use .iter_mut() on this one vector2.iter_mut().for_each(|x| *x +=100); println!("{:?}", vector1_a); println!("{:?}", vector2); println!("{:?}", vector1_b); }
This prints:
[2, 3, 4]
[110, 120, 130]
[10, 20, 30]
The first two we used a method called .map()
. This method lets you do something to every item, then pass it on. The last one we used is one called .for_each()
. This method just lets you do something to every item. .iter_mut()
plus for_each()
is basically just a for
loop. Inside each method we can give a name to every item (we just called it x
) and use that to change it. These are called closures and we will learn about them in the next section.
Let's go over them again, one at a time.
First we used .iter()
on vector1
to get references. We added 1 to each, and made it into a new Vec. vector1
is still alive because we only used references: we didn't take by value. Now we have vector1
, and a new Vec called vector1_a
. Because .map()
just passes it on, we needed to use .collect()
to make it into a Vec
.
Then we used into_iter
to get an iterator by value from vector1
. This destroys vector1
, because that's what into_iter()
does. So after we make vector1_b
we can't use vector1
again.
Finally we used .iter_mut()
for vector2
. It is mutable, so we don't need to use .collect()
to create a new Vec. Instead, we change the values in the same Vec with mutable references. So vector2
is still there. Because we don't need a new Vec, we use for_each
: it's just like a for
loop.
How an iterator works
An iterator works by using a method called .next()
, which gives an Option
. When you use an iterator, Rust calls next()
over and over again. If it gets Some
, it keeps going. If it gets None
, it stops.
Do you remember the assert_eq!
macro? In documentation, you see it all the time. Here it is showing how an iterator works.
fn main() { let my_vec = vec!['a', 'b', '거', '柳']; // Just a regular Vec let mut my_vec_iter = my_vec.iter(); // This is an Iterator type now, but we haven't called it yet assert_eq!(my_vec_iter.next(), Some(&'a')); // Call the first item with .next() assert_eq!(my_vec_iter.next(), Some(&'b')); // Call the next assert_eq!(my_vec_iter.next(), Some(&'거')); // Again assert_eq!(my_vec_iter.next(), Some(&'柳')); // Again assert_eq!(my_vec_iter.next(), None); // Nothing is left: just None assert_eq!(my_vec_iter.next(), None); // You can keep calling .next() but it will always be None }
Implementing Iterator
for your own struct or enum is not too hard. First let's make a book library and think about it.
#[derive(Debug)] // we want to print it with {:?} struct Library { library_type: LibraryType, // this is our enum books: Vec<String>, // list of books } #[derive(Debug)] enum LibraryType { // libraries can be city libraries or country libraries City, Country, } impl Library { fn add_book(&mut self, book: &str) { // we use add_book to add new books self.books.push(book.to_string()); // we take a &str and turn it into a String, then add it to the Vec } fn new() -> Self { // this creates a new Library Self { library_type: LibraryType::City, // most are in the city so we'll choose City // most of the time books: Vec::new(), } } } fn main() { let mut my_library = Library::new(); // make a new library my_library.add_book("The Doom of the Darksword"); // add some books my_library.add_book("Demian - die Geschichte einer Jugend"); my_library.add_book("구운몽"); my_library.add_book("吾輩は猫である"); println!("{:?}", my_library.books); // we can print our list of books }
That works well. Now we want to implement Iterator
for the library so we can use it in a for
loop. Right now if we try a for
loop, it doesn't work:
#![allow(unused)] fn main() { for item in my_library { println!("{}", item); // ⚠️ } }
It says:
error[E0277]: `Library` is not an iterator
--> src\main.rs:47:16
|
47 | for item in my_library {
| ^^^^^^^^^^ `Library` is not an iterator
|
= help: the trait `std::iter::Iterator` is not implemented for `Library`
= note: required by `std::iter::IntoIterator::into_iter`
But we can make library into an iterator with impl Iterator for Library
. Information on the Iterator
trait is here in the standard library: https://doc.rust-lang.org/std/iter/trait.Iterator.html
On the top left of the page it says: Associated Types: Item
and Required Methods: next
. An "associated type" means "a type that goes together". Our associated type will be String
, because we want the iterator to give us Strings.
In the page it has an example that looks like this:
// an iterator which alternates between Some and None struct Alternate { state: i32, } impl Iterator for Alternate { type Item = i32; fn next(&mut self) -> Option<i32> { let val = self.state; self.state = self.state + 1; // if it's even, Some(i32), else None if val % 2 == 0 { Some(val) } else { None } } } fn main() {}
You can see that under impl Iterator for Alternate
it says type Item = i32
. This is the associated type. Our iterator will be for our list of books, which is a Vec<String>
. When we call next, it will give us a String
. So we will write type Item = String;
. That is the associated item.
To implement Iterator
, you need to write the fn next()
function. This is where you decide what the iterator should do. For our Library
, we want it to give us the last books first. So we will match
with .pop()
which takes the last item off if it is Some
. We also want to print " is found!" for each item. Now it looks like this:
#[derive(Debug, Clone)] struct Library { library_type: LibraryType, books: Vec<String>, } #[derive(Debug, Clone)] enum LibraryType { City, Country, } impl Library { fn add_book(&mut self, book: &str) { self.books.push(book.to_string()); } fn new() -> Self { Self { library_type: LibraryType::City, // most of the time books: Vec::new(), } } } impl Iterator for Library { type Item = String; fn next(&mut self) -> Option<String> { match self.books.pop() { Some(book) => Some(book + " is found!"), // Rust allows String + &str None => None, } } } fn main() { let mut my_library = Library::new(); my_library.add_book("The Doom of the Darksword"); my_library.add_book("Demian - die Geschichte einer Jugend"); my_library.add_book("구운몽"); my_library.add_book("吾輩は猫である"); for item in my_library.clone() { // we can use a for loop now. Give it a clone so Library won't be destroyed println!("{}", item); } }
This prints:
吾輩は猫である is found!
구운몽 is found!
Demian - die Geschichte einer Jugend is found!
The Doom of the Darksword is found!