#255 Jul 12, 2026

255. rotate_left / rotate_right — Bit Rotation Without the Shift-Overflow Trap

Shifting throws bits away. The manual “wrap them around” idiom panics on n == 0. Rotation has been one method call the whole time.

The problem with shifts

A left shift pushes the top bits off the edge — they’re gone:

1
2
3
let x = 0b1000_0001_u8;

assert_eq!(x << 1, 0b0000_0010); // top bit lost

When you need the bits to wrap around — hashing, checksums, circular counters — the textbook idiom combines two shifts:

1
2
3
let n = 1;
let rotated = (x << n) | (x >> (8 - n));
assert_eq!(rotated, 0b0000_0011);

Which works right up until n == 0: then x >> 8 is a shift by the full bit width — a panic in debug builds, and a masked, silently-wrong result in release. A correct version needs masking both shift amounts, and now you’re writing a code comment again.

The built-in

Every integer type has rotate_left and rotate_right. Bits that fall off one end come back on the other:

1
2
3
4
let x = 0b1000_0001_u8;

assert_eq!(x.rotate_left(1),  0b0000_0011);
assert_eq!(x.rotate_right(1), 0b1100_0000);

No edge cases: the rotation amount is taken modulo the bit width, so n == 0, n == 8, even n == 1000 are all fine —

1
2
3
assert_eq!(x.rotate_left(0), x);
assert_eq!(x.rotate_left(8), x);           // full circle
assert_eq!(x.rotate_left(9), x.rotate_left(1));

— and like bite 254’s isolate_lowest_one, it compiles to a single instruction (ROL/ROR on x86) instead of the three ops the manual idiom costs.

Round trips for free

Rotation never destroys information, so it’s trivially reversible — handy for mixing bits in a hash and for tests:

1
2
3
let v = 0xDEAD_u16;

assert_eq!(v.rotate_left(5).rotate_right(5), v);

Note this rotates the bit pattern, not bytes: for endianness work you want swap_bytes or bite 240’s to_le_bytes. But when the task is “slide bits around a circle”, rotate_left says exactly that — with no (8 - n) waiting to panic.

← Previous 254. isolate_lowest_one — The x & x.wrapping_neg() Hack Finally Has a Name