u16 is Rust’s unsigned 16-bit integer type. Its values run from 0 through 65,535. Each value has a fixed number of bits: zero is sixteen 0s, while 65,535 is sixteen 1s. There is no u16 bit pattern for 65,536. section 5.2
Count past the limit
Start with `let mut i: u16 = 0` and repeatedly run `i += 1000`. After printing 65,000, the next addition exceeds the type’s range. With the shown default `rustc` build, the program panics with `attempt to add with overflow`. The problem is not that the number is imprecise: it cannot fit. section 5.2
A fixed width is useful when a value represents a fixed-width instruction. A CHIP-8 opcode is a u16 made of two bytes, or four 4-bit nibbles. For example, `0x8014` can be split into `8`, `0`, `1`, and `4`; those parts help an emulator choose an operation and its registers. section 5.7.1
Listing 5.24 Reading an opcode from memory
8 fn read_opcode(&self) -> u16 {
9 let p = self.position_in_memory;
10 let op_byte1 = self.memory[p] as u16;
11 let op_byte2 = self.memory[p + 1] as u16;
12
13 op_byte1 << 8 | op_byte2 #1
14 }
Follow how `read_opcode()` casts two adjacent memory bytes to u16, shifts the first byte left by eight bits, and combines them with `|` into one opcode. section 5.7.3
Once the opcode is assembled, masks and right shifts select its parts. For `0x71E4`, `(opcode & 0xF000) >> 12` gives `0x7`, while `opcode & 0x0FFF` gives the 12-bit value `0x1E4`. The same emulator keeps its calculation registers as u8 values and uses `overflowing_add()` when their sums might exceed that smaller type. section 5.7.1 section 5.7.3
Instructions can also be written as a `[u16; 3]` array, then separated into six bytes for storage in memory. A call stack uses `[u16; 16]` to hold return addresses, converting between u16 addresses and the CPU’s `usize` memory position. section 5.7.4
