Destructuring

When implementing draw_sprite, we need to iterate over each row of a sprite and know two things simultaneously: the row index (to calculate the y position on screen) and the byte value (to extract the pixels). In many languages, you would get one and calculate the other. In Rust, you get both at once — directly in the for loop header — through destructuring.

Destructuring is the ability to unpack a value into its component parts in a single step. It works wherever a value is bound to a name: let statements, for loops, function parameters, and match arms.

Destructuring tuples in let

The most common form — pulling apart a tuple returned by a function:

#![allow(unused)]
fn main() {
let (result, carry) = self.v[x].overflowing_add(self.v[y]);
}

overflowing_add returns (u8, bool). Without destructuring, you would write:

#![allow(unused)]
fn main() {
let pair   = self.v[x].overflowing_add(self.v[y]);
let result = pair.0;
let carry  = pair.1;
}

Three lines instead of one, and .0 / .1 tell you nothing about what the values mean. Destructuring names them at the point of creation.

Destructuring in for loops

#![allow(unused)]
fn main() {
for (row, &byte) in sprite.iter().enumerate() {
    // row:  usize — the row index (0, 1, 2, ...)
    // byte: u8    — the sprite byte for this row
}
}

Two things are happening at once here:

Tuple destructuring — .enumerate() yields (usize, &u8) tuples. The (row, &byte) pattern unpacks that tuple directly in the loop header.

Reference pattern — .iter() yields references (&u8). Writing &byte in the pattern matches the reference and binds byte as a plain u8 — the dereference happens in the pattern, not in the loop body. Without it:

#![allow(unused)]
fn main() {
for (row, byte) in sprite.iter().enumerate() {
    let b = *byte;  // byte is &u8 — manual dereference needed every time
}
}

The &byte pattern is an idiom worth getting comfortable with — it appears in any loop over a borrowed collection where you want the value rather than the reference.

Destructuring in match

Match arms can destructure the matched value:

#![allow(unused)]
fn main() {
match point {
    (0, 0)  => println!("origin"),
    (x, 0)  => println!("on x-axis at {}", x),
    (0, y)  => println!("on y-axis at {}", y),
    (x, y)  => println!("at ({}, {})", x, y),
}
}

The variables x and y in the arms are bound from the matched tuple — no separate extraction step needed.

Ignoring parts with _

#![allow(unused)]
fn main() {
let (value, _) = some_tuple;  // we only need the first element
}

_ discards a part without binding it. The compiler also suppresses unused variable warnings for anything prefixed with _.


In the emulator: The for (row, &byte) pattern is used in draw_sprite to iterate over sprite rows. See The Display for the full implementation and DRW — Drawing Sprites for the opcode that calls it. The let (result, carry) = overflowing_add(...) pattern is used in 8XY* — Arithmetic and Logic.