Iterators

An iterator is an object that returns the items of a collection in sequence, from the first to the last. To return the following item, it uses a next() method. Here, we have an opportunity to use an Option. Because an iterator can have no more values at the last next() call, next() always returns an Option, Some(value) when there is a value and None when there are no more values to return.

The simplest object that has this behavior is a range of numbers 0..n (remember n is excluded). Every time we used a for loop like for i in 0..n the underlying iterator mechanism was put to work. Let's see an example:

// see code in Chapter 5/code/iterators.rs 
let mut rng = 0..7; 
println!("> {:?}", rng.next()); // 
println!("> {:?}", rng.next()); // 
for n in rng { 
    print!("{} - ", n); 
} // prints 2 - 3 - 4 - 5 - 6 

This prints the following output:

    Some(0)
    Some(1)

We see here the function next() at work, producing 0 and 1, and so on; the for loop continues until the end.

Exercise:
In the previous example we saw that the function next() returns a Some type value, a variant of type Option (see the Result and Option section in Chapter 4, Structuring Data and Matching Patterns). Write an endless loop over the range rng with the function next() and see what happens. How would you break the endless loop? Use a match on the Option value (for an example, see Chapter 5/exercises/range_next.rs). In fact, the for loop we saw right before this exercise is syntactic sugar for this loop - match construct.

Iterators are also the preferred way to loop over arrays or slices. Let's revisit the aliens array from Chapter 4, Structuring Data and Matching Patterns:

let aliens = ["Cherfer", "Fynock", "Shirack", "Zuxu"];" 

Instead of using the index to show all the items one by one, let's do it the iterator way with the iter() function:

for alien in aliens.iter() { 
   print!("{} / ", alien) 
   // process alien 
} 

This prints out the following output:

    Cherfer / Fynock / Shirack / Zuxu /  

The alien variable is of type &str, a reference to each of the items in turn (technically, it is here of type &str, because the items themselves are of type &str, but this is not relevant to the point being made here). This is much more performant and safe than using an index, because now Rust doesn't need to do index-bounds checking, we're always certain to move within the memory of the array.

An even shorter way is to write the following:

for alien in &aliens { 
    print!("{} / ", alien) 
} 

The variable alien is also of type &str, but the print! macro automatically dereferences this. If you want them to print out in reverse order, do aliens.iter().rev(). Other iterators we encountered in the previous chapter, where the chars() and split() methods on Strings.

Iterators are lazy by nature, they do not generate values unless asked and we ask by calling the next() method or applying a for in loop. That makes sense, we don't want to allocate one million integers in the following binding:

let rng = 0..1000_000; // _ makes the number 1000000 more readable 

We want to allocate memory only when we need it.