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 indraw_spriteto iterate over sprite rows with their index. The(0..n).map(...).collect()pattern builds the sprite byte array in theDXYNopcode. See DRW — Drawing Sprites and The Display. Thecollect()step requires understanding Vec<T>.