Iterators

An iterator produces a sequence of values one at a time. Rust's standard library wraps this in a rich set of composable methods.

.iter() and .enumerate()

.iter() produces an iterator over references to a collection's elements. .enumerate() wraps any iterator to also produce the current index, yielding (index, value) tuples:

#![allow(unused)]
fn main() {
let bytes = [0xFF, 0x81, 0xFF];

for (i, byte) in bytes.iter().enumerate() {
    // i: usize, byte: &u8
    println!("row {}: {:08b}", i, byte);
}
}

Without .enumerate(), you would need a manual counter:

#![allow(unused)]
fn main() {
let mut i = 0;
for byte in bytes.iter() {
    // ...
    i += 1;
}
}

Common iterator methods

#![allow(unused)]
fn main() {
let numbers = [1, 2, 3, 4, 5];

numbers.iter().map(|x| x * 2)           // transform each element
numbers.iter().filter(|&&x| x > 2)      // keep matching elements
numbers.iter().sum::<i32>()             // fold into one value: 15
numbers.iter().any(|&x| x > 4)         // true if any match
numbers.iter().all(|&x| x > 0)         // true if all match
}

Iterators are lazy

Chaining methods does not execute anything:

#![allow(unused)]
fn main() {
// nothing runs here
let chain = numbers.iter()
    .filter(|&&x| x > 2)
    .map(|x| x * 2);

// execution happens when consumed
let result: Vec<i32> = chain.collect();
}

Building a Vec from a range

#![allow(unused)]
fn main() {
let sprite: Vec<u8> = (0..n)
    .map(|row| self.memory.read(self.i + row as u16))
    .collect();
}

(0..n) is a range iterator. .map() transforms each index into a memory read. .collect() gathers the results into a Vec<u8>.


In the emulator: .enumerate() is used in draw_sprite to iterate over sprite rows with their index. The (0..n).map(...).collect() pattern builds the sprite byte array in the DXYN opcode. See DRW — Drawing Sprites and The Display. The collect() step requires understanding Vec<T>.