Ownership in Closures
Closures can capture variables from their surrounding scope. How they capture — by reference or by value — follows Rust's ownership rules, and the distinction matters when a closure is called multiple times.
The error we hit
When building the pixel buffer, the first attempt used an iterator chain:
#![allow(unused)] fn main() { // FAILS — cpu.display is moved on the first iteration, gone on the second let buffer: Vec<u32> = (0..HEIGHT) .flat_map(|y| { (0..WIDTH).map(move |x| { if cpu.display.pixel(x, y) { 0xFFFFFFFF } else { 0xFF000000 } }) }) .collect(); }
The compiler error: "cannot move out of cpu.display, a captured variable in an FnMut closure."
flat_map calls its closure once per row — 32 times. The inner move closure attempts to take ownership of cpu.display on every call. But ownership can only transfer once. After the first call, cpu.display is gone.
The fix: a plain loop borrows by reference
#![allow(unused)] fn main() { let mut buffer = vec![0u32; WIDTH * HEIGHT]; for y in 0..HEIGHT { for x in 0..WIDTH { buffer[y * WIDTH + x] = if cpu.display.pixel(x, y) { 0xFFFFFFFF } else { 0xFF000000 }; } } }
A for loop borrows cpu.display by reference on each iteration — no ownership transfer, no problem.
Why move exists
move closures take ownership of captured variables. They are necessary when the closure outlives the scope where it was created — for example, spawning a thread:
#![allow(unused)] fn main() { let data = vec![1, 2, 3]; std::thread::spawn(move || { println!("{:?}", data); // data is moved into the thread }); }
Without move, the thread could outlive data — Rust rejects this at compile time.
Three ways closures capture
| Mode | Syntax | When used |
|---|---|---|
| Immutable reference | default | closure only reads |
| Mutable reference | default when needed | closure modifies |
| By value (move) | move || { } | closure must own the value |
Rust chooses the least restrictive mode automatically. move overrides this.
The rule
A value can only be moved once. If a closure is called multiple times (like inside flat_map), it cannot move the same value on each call — the value would be gone after the first. References are Copy and can be used any number of times; owned values cannot.
In the emulator: This error was encountered when building the render loop in
main.rs. The plainforloop solution is what lives in the final code. See The Main Loop for the full context.