Iteration

During the very first exercises, you learned that Rust lets you iterate over collections using for loops. We were looking at ranges at that point (e.g. 0..5), but the same holds true for collections like arrays and vectors.

// It works for `Vec`s
let v = vec![1, 2, 3];
for n in v {
    println!("{}", n);
}

// It also works for arrays
let a: [u32; 3] = [1, 2, 3];
for n in a {
    println!("{}", n);
}

It's time to understand how this works under the hood.

for desugaring

Every time you write a for loop in Rust, the compiler desugars it into the following code:

let mut iter = IntoIterator::into_iter(v);
loop {
    match iter.next() {
        Some(n) => {
            println!("{}", n);
        }
        None => break,
    }
}

loop is another looping construct, on top of for and while.
A loop block will run forever, unless you explicitly break out of it.

Iterator trait

The next method in the previous code snippet comes from the Iterator trait. The Iterator trait is defined in Rust's standard library and provides a shared interface for types that can produce a sequence of values:

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

The Item associated type specifies the type of the values produced by the iterator.

next returns the next value in the sequence.
It returns Some(value) if there's a value to return, and None when there isn't.

Be careful: there is no guarantee that an iterator is exhausted when it returns None. That's only guaranteed if the iterator implements the (more restrictive) FusedIterator trait.

IntoIterator trait

Not all types implement Iterator, but many can be converted into a type that does.
That's where the IntoIterator trait comes in:

trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;
    fn into_iter(self) -> Self::IntoIter;
}

The into_iter method consumes the original value and returns an iterator over its elements.
A type can only have one implementation of IntoIterator: there can be no ambiguity as to what for should desugar to.

One detail: every type that implements Iterator automatically implements IntoIterator as well. They just return themselves from into_iter!

Bounds checks

Iterating over iterators has a nice side effect: you can't go out of bounds, by design.
This allows Rust to remove bounds checks from the generated machine code, making iteration faster.

In other words,

let v = vec![1, 2, 3];
for n in v {
    println!("{}", n);
}

is usually faster than

let v = vec![1, 2, 3];
for i in 0..v.len() {
    println!("{}", v[i]);
}

There are exceptions to this rule: the compiler can sometimes prove that you're not going out of bounds even with manual indexing, thus removing the bounds checks anyway. But in general, prefer iteration to indexing where possible.

Exercise

The exercise for this section is located in 06_ticket_management/04_iterators