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!