Integers

#255 Jul 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.

#254 Jul 2026

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

Every bitmask codebase has an unexplained x & x.wrapping_neg() in it somewhere. Rust 1.97 gives the trick a name — and a sibling for the other end.

The folklore version

To keep only the lowest set bit of an integer, the two’s-complement trick is to AND the value with its own negation:

1
2
3
4
let x = 0b0101_0100_u8;

// lowest set bit, the folklore way
assert_eq!(x & x.wrapping_neg(), 0b0000_0100);

It works, it compiles to one instruction (BLSI on x86) — and it explains nothing to the next reader. For the highest set bit there isn’t even a one-liner: you shift 1 by leading_zeros arithmetic and special-case zero.

Named, on every integer type

Rust 1.97 stabilizes isolate_lowest_one and isolate_highest_one. They return the isolated bit as a mask — the value with all other bits cleared:

1
2
3
4
5
6
7
8
let x = 0b0101_0100_u8;

assert_eq!(x.isolate_lowest_one(),  0b0000_0100);
assert_eq!(x.isolate_highest_one(), 0b0100_0000);

// zero just stays zero — no panic, no sentinel
assert_eq!(0_u8.isolate_lowest_one(),  0);
assert_eq!(0_u8.isolate_highest_one(), 0);

Where bite 250’s lowest_one / highest_one answer “at which position?” (as an Option), the isolate_ pair answers “which bit?” — same information, shaped for masking instead of indexing.

The pattern: walk the set bits

The mask shape is exactly what you want for iterating over flags — grab the lowest bit, handle it, XOR it away:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let mut mask = 0b0101_0100_u8;
let mut seen = vec![];

while mask != 0 {
    let bit = mask.isolate_lowest_one();
    seen.push(bit);   // handle one flag
    mask ^= bit;      // clear it
}

assert_eq!(seen, [0b0000_0100,
                  0b0001_0000,
                  0b0100_0000]);

No positions, no shifting back and forth — each iteration hands you a ready-to-use single-bit mask. Signed types work too ((-8_i8).isolate_lowest_one() == 8), since the methods operate on the raw bit pattern.

If your code review comments still include “this ANDs x with its negation to isolate the lowest set bit…”, Rust 1.97 lets the method name say it for you.

#253 Jul 2026

253. overflowing_add — Wrap, But Know It Happened

This morning’s Wrapping<T> (bite 252) wraps silently — but multiword arithmetic needs the carry bit too. overflowing_add returns both: the wrapped result and whether it wrapped.

The sum < a trick

The classic way to detect a carry after unsigned addition compares the result to an input:

1
2
3
4
5
fn add_with_carry(a: u64, b: u64) -> (u64, u64) {
    let sum = a.wrapping_add(b);
    let carry = (sum < a) as u64; // wrapped iff smaller
    (sum, carry)
}

It works — unsigned overflow means the sum came back smaller — but it’s a puzzle for the reader, it’s easy to compare against the wrong operand, and the signed version of the trick is different and wrong in edge cases.

overflowing_add says it directly

Every integer type has overflowing_add (and _sub, _mul, _neg, _shl…): it returns (wrapped_value, overflowed: bool) in one call:

1
2
assert_eq!(u64::MAX.overflowing_add(1), (0, true));
assert_eq!(1_u64.overflowing_add(1), (2, false));

That makes carry chains — the core of any 128-bit-or-wider addition — read like what they are:

1
2
3
4
5
6
7
8
// [lo, hi] little-endian u128 as two u64 limbs
fn add128(a: [u64; 2], b: [u64; 2]) -> [u64; 2] {
    let (lo, carry) = a[0].overflowing_add(b[0]);
    let hi = a[1]
        .wrapping_add(b[1])
        .wrapping_add(carry as u64);
    [lo, hi]
}
1
2
3
let max = [u64::MAX, 0];      // u64::MAX
let one = [1, 0];
assert_eq!(add128(max, one), [0, 1]); // 2^64

One family, four answers to overflow

Where checked_add (and bite 219’s checked_add_signed) bails out with None and Wrapping<T> (bite 252) wraps silently, overflowing_add is the “do both” option: you always get the mod-2^n result, plus the fact you’d otherwise have to reverse-engineer. (The dedicated carrying_add that takes and returns a carry is still nightly-only — on stable, overflowing_add is how the limbs get added.)

Compilers recognize the pattern, too: the carry chain above compiles down to an add + adc pair on x86-64 — the same code the manual trick produces, minus the puzzle.

#252 Jul 2026

252. Wrapping<T> — Modular Arithmetic Without the .wrapping_add() Noise

Hash functions, checksums, and PRNGs want arithmetic mod 2^n — but writing .wrapping_mul() on every single operation buries the math. std::num::Wrapping<T> puts the semantics in the type so you can use plain operators again.

Method-call soup

In wrap-heavy code every operation needs the method form, or debug builds panic on overflow:

1
2
3
4
5
6
7
8
fn fnv1a(data: &[u8]) -> u32 {
    let mut h: u32 = 0x811c9dc5;
    for &b in data {
        h ^= b as u32;
        h = h.wrapping_mul(0x01000193);
    }
    h
}

One call is fine. But real hash/PRNG code chains them — x.wrapping_mul(a).wrapping_add(c) — and the actual formula disappears under the method names. Forget one and a debug build panics while release silently wraps.

Wrapping moves the choice into the type

Wrap the values once; every operator on them wraps from then on:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::num::Wrapping;

fn fnv1a(data: &[u8]) -> u32 {
    let mut h = Wrapping(0x811c9dc5_u32);
    for &b in data {
        h ^= Wrapping(b as u32);
        h *= Wrapping(0x01000193);
    }
    h.0
}

*, +, -, <<, the assign forms, even unary - — all defined to wrap, in every build profile. .0 unwraps back to the plain integer at the boundary.

1
2
3
4
5
use std::num::Wrapping;

let x = Wrapping(u32::MAX);
assert_eq!(x + Wrapping(1), Wrapping(0));
assert_eq!(-Wrapping(1_u32), Wrapping(u32::MAX));

Declare intent once, not per call

The win is the same one Saturating<T> gives you (bite 167): overflow behavior is a property of the data, declared once, instead of a per-callsite decision you can fumble. The type keeps you honest, too — you can’t accidentally mix a wrapping value into checked arithmetic without an explicit .0.

Stable since Rust 1.0, Copy, zero-cost: Wrapping<u32> compiles to exactly the same instructions as the wrapping_* calls. If a function is more math than method names, wrap the inputs and let the operators speak.

#250 Jul 2026

250. lowest_one / highest_one — Set-Bit Positions as an Option, Not a Sentinel

trailing_zeros() on 0 returns the type’s width — a sentinel you must remember to special-case. Rust 1.97 adds lowest_one and highest_one, which return an Option and make “no bits set” impossible to forget.

The sentinel problem

This morning’s bite 249 covered bit_width from today’s Rust 1.97 release. The same release fixes another sharp edge: finding the position of a set bit.

The classic tools each handle “no bits set” badly:

1
2
3
4
5
6
7
8
let x = 0b0101_0100_u8;

// Position of the lowest set bit... usually.
assert_eq!(x.trailing_zeros(), 2);

// On zero it returns the type width — a magic
// number you must remember to check for:
assert_eq!(0_u8.trailing_zeros(), 8);

For the highest bit it’s worse: ilog2() panics on zero, and the leading_zeros arithmetic bakes the type width into your formula — the same trap bite 249 described.

An Option is honest

Stable since Rust 1.97 on all integer types:

1
2
3
4
5
6
7
8
let x = 0b0101_0100_u8;

assert_eq!(x.lowest_one(),  Some(2));
assert_eq!(x.highest_one(), Some(6));

// Zero has no set bits — and the type says so:
assert_eq!(0_u8.lowest_one(),  None);
assert_eq!(0_u8.highest_one(), None);

No sentinel, no panic. The compiler forces you to decide what “no bits” means for your code, instead of letting 8 masquerade as a bit position:

1
2
3
4
5
6
7
8
// e.g. first free slot in an allocation bitmap,
// where a full mask means "grow"
let free_mask = 0b0000_0000_u8;

match free_mask.lowest_one() {
    Some(slot) => println!("use slot {slot}"),
    None       => println!("all full, grow"),
}

The 1.97 bit family

Together with bite 249, the release completes a tidy family: bit_width (how many bits a value needs), isolate_lowest_one / isolate_highest_one (the bit as a mask), and lowest_one / highest_one (the bit as a position). They agree with each other, too:

1
2
3
4
let x = 0b0101_0100_u8;

assert_eq!(x.highest_one().map(|p| p + 1)
            .unwrap_or(0), x.bit_width());

If you still write x & x.wrapping_neg() or 31 - n.leading_zeros() from muscle memory, Rust 1.97 is your cue to stop.

#249 Jul 2026

249. bit_width — How Many Bits Does This Number Need? (New in Rust 1.97)

“How many bits do I need for this value?” used to mean 32 - n.leading_zeros() — a formula you rewrite every time the integer type changes. Rust 1.97 — hitting stable today — ships bit_width and friends.

The old dance

Bit packing, varint encoding, sizing a field: they all start with the same question — the position of the highest set bit, plus one. The classic answers both have sharp edges:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let n = 300_u32;

// Formula depends on the type's width —
// silently wrong after a switch to u64:
let bits = 32 - n.leading_zeros();

// Reads better, but panics on 0:
let bits2 = n.ilog2() + 1;

assert_eq!(bits, 9);
assert_eq!(bits2, 9);

The leading_zeros version encodes the type width (32) by hand. The ilog2 version blows up on zero, so real code needs an if n == 0 guard around it.

bit_width says what you mean

Stable since Rust 1.97 on all integers:

1
2
3
4
5
assert_eq!(0_u32.bit_width(), 0);
assert_eq!(1_u32.bit_width(), 1);
assert_eq!(255_u32.bit_width(), 8);
assert_eq!(256_u32.bit_width(), 9);
assert_eq!(u32::MAX.bit_width(), 32);

Zero needs zero bits — no panic, no guard. The width of the type is no longer baked into your arithmetic, so the same line keeps working when a refactor turns u32 into u64.

Bonus: isolate_lowest_one kills the wrapping_neg hack

The same release stabilizes isolate_lowest_one and isolate_highest_one, which keep just the lowest or highest set bit. The lowest-bit version replaces the venerable x & x.wrapping_neg() two’s-complement trick — the one you either know or google every time:

1
2
3
4
5
let x = 0b1011000_u32;

assert_eq!(x.isolate_lowest_one(),  0b0001000);
assert_eq!(x.isolate_highest_one(), 0b1000000);
assert_eq!(0_u32.isolate_lowest_one(), 0);

That makes the standard “iterate the set bits” loop readable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let mut bits = 0b1001_0110_u32;
let mut positions = Vec::new();

while bits != 0 {
    let low = bits.isolate_lowest_one();
    positions.push(low.trailing_zeros());
    bits ^= low; // clear the bit we handled
}

assert_eq!(positions, [1, 2, 4, 7]);

No two’s-complement folklore in sight — the intent is right there in the method names.

#248 Jul 2026

248. is_multiple_of — Divisibility That Won't Panic on Zero

x % y == 0 is the divisibility check everyone writes — and it panics the moment y is zero. is_multiple_of says what you mean and returns false instead of blowing up.

The hidden panic in % == 0

The modulo idiom works until the divisor comes from outside your control — a config value, a user-supplied chunk size, a computed stride:

1
2
3
4
5
6
fn fits_evenly(len: usize, block: usize) -> bool {
    len % block == 0
}

fits_evenly(12, 4); // true
fits_evenly(12, 0); // panic: remainder with a divisor of zero

A validation helper that itself crashes on bad input is exactly backwards.

is_multiple_of handles zero for you

Stable since Rust 1.87 on all unsigned integers:

1
2
3
4
5
6
assert!(12_u32.is_multiple_of(3));
assert!(!13_u32.is_multiple_of(3));

// No panic — just the mathematically honest answer:
assert!(!12_u32.is_multiple_of(0));
assert!(0_u32.is_multiple_of(0)); // 0 = 0 * anything

n.is_multiple_of(m) is true exactly when some k satisfies n == k * m. Zero is a multiple of everything (including zero), and nothing else is a multiple of zero. No branch you have to remember to write, no if block != 0 && guard cluttering the call site.

Reads like the sentence you meant

Even when the divisor can never be zero, the method wins on intent. Compare a leap-year-style rule:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let year = 2026_u32;

// Which of these is the "divisible" and
// which is the "not divisible" arm again?
let a = year % 4 == 0 && year % 100 != 0;

let b = year.is_multiple_of(4)
    && !year.is_multiple_of(100);

assert_eq!(a, b);
assert!(!b); // 2026: not a leap year

The name carries the meaning; the == 0 version makes every reader re-derive it. Clippy agrees — recent versions lint x % y == 0 as manual_is_multiple_of.

One caveat: it’s unsigned-only (u8 through u128, plus usize). For signed integers you’re still on % — pair it with a zero check yourself.

223. count_ones — Count Set Bits Without a Loop

Need to count how many bits are set in an integer — flags in a bitmask, a population count, a Hamming weight? Don’t write a shift-and-mask loop. Every integer type has .count_ones(), and it usually lowers to a single CPU instruction.

The hand-rolled version is a loop that masks the low bit and shifts:

1
2
3
4
5
6
7
8
fn count_set_bits(mut n: u32) -> u32 {
    let mut count = 0;
    while n != 0 {
        count += n & 1;
        n >>= 1;
    }
    count
}

It works, but it’s a loop you have to get right, and it’s slower than the hardware can do the same job.

Enter count_ones

1
2
let flags: u32 = 0b1011_0010;
assert_eq!(flags.count_ones(), 4);

One call. It’s available on every integer type (u8..u128, i8..i128), and on most targets it compiles straight to a popcnt instruction.

Where it earns its keep

Hamming distance — XOR two values, then count the bits that differ:

1
2
3
let a: u8 = 0b1100_1010;
let b: u8 = 0b1001_1011;
assert_eq!((a ^ b).count_ones(), 3);

Power-of-two test — a power of two has exactly one bit set:

1
2
3
4
5
6
fn is_power_of_two(n: u32) -> bool {
    n.count_ones() == 1
}

assert!(is_power_of_two(64));
assert!(!is_power_of_two(48));

The rest of the family

count_zeros, leading_zeros, trailing_zeros, leading_ones, and trailing_ones round it out — all single-instruction on modern CPUs. leading_zeros is the trick behind a fast integer log2; trailing_zeros gives you the index of the lowest set bit:

1
2
assert_eq!(0b0010_1000u8.trailing_zeros(), 3); // lowest set bit at index 3
assert_eq!(0b0000_1111u8.count_zeros(), 4);

Next time you reach for a bit-counting loop, reach for count_ones instead. Stable since Rust 1.0.

#221 Jun 2026

221. from_str_radix — Parse Hex, Binary, or Octal Without Hand-Rolling a Loop

Got a "ff" or a "1010" and need the number behind it? Don’t loop over the characters multiplying by 16. Every integer type has from_str_radix, which parses a string in any base from 2 to 36 in one call.

The hand-rolled version is easy to get subtly wrong — overflow, bad digits, off-by-one on the place value:

1
2
3
4
5
6
7
8
fn parse_hex(s: &str) -> u32 {
    let mut n = 0u32;
    for c in s.chars() {
        n = n * 16 + c.to_digit(16).unwrap();
    }
    n
}
let _ = parse_hex("ff"); // works, but silently overflows on long input

from_str_radix does the whole thing, and returns a Result so bad input is an error instead of a panic or a wrong answer:

1
2
3
4
5
6
7
8
let n = u32::from_str_radix("ff", 16).unwrap();
assert_eq!(n, 255);

let b = u8::from_str_radix("1010", 2).unwrap();
assert_eq!(b, 10);

let o = u16::from_str_radix("755", 8).unwrap();
assert_eq!(o, 493);

It validates digits for you — a character outside the chosen base is a clean Err, not garbage:

1
2
assert!(u32::from_str_radix("xyz", 16).is_err());
assert!(u8::from_str_radix("2", 2).is_err()); // '2' isn't a binary digit

A real use: cracking a #RRGGBB color into channels. Slice, parse, done — no manual nibble math:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn rgb(hex: &str) -> Option<(u8, u8, u8)> {
    let h = hex.strip_prefix('#').unwrap_or(hex);
    if h.len() != 6 { return None; }
    let r = u8::from_str_radix(&h[0..2], 16).ok()?;
    let g = u8::from_str_radix(&h[2..4], 16).ok()?;
    let b = u8::from_str_radix(&h[4..6], 16).ok()?;
    Some((r, g, b))
}

assert_eq!(rgb("#E8593C"), Some((232, 89, 60)));
assert_eq!(rgb("oops"), None);

Signed types work too, and a leading - is honored:

1
assert_eq!(i32::from_str_radix("-2a", 16), Ok(-42));

For plain base-10 you don’t even need it — "42".parse::<u32>() is the same thing with the radix fixed at 10. Reach for from_str_radix the moment the base isn’t ten.

#220 Jun 2026

220. ilog10 — Count an Integer's Digits Without Formatting It to a String

Need to know how many digits a number has? Reaching for n.to_string().len() allocates a whole String just to measure it. ilog10 answers the same question with one instruction and zero allocation.

The classic way to count digits builds a string and throws it away:

1
2
3
let n: u32 = 4096;
let digits = n.to_string().len(); // heap-allocates a String to count 4 chars
assert_eq!(digits, 4);

ilog10 returns the floor of the base-10 logarithm, so the digit count is just that plus one — no allocation, no formatting:

1
2
3
let n: u32 = 4096;
let digits = n.ilog10() + 1;
assert_eq!(digits, 4);

The one catch: 0 has no logarithm, so 0u32.ilog10() panics. Guard the zero case, since “0” still has one digit:

1
2
3
4
5
6
7
fn digit_count(n: u32) -> u32 {
    if n == 0 { 1 } else { n.ilog10() + 1 }
}

assert_eq!(digit_count(0), 1);
assert_eq!(digit_count(7), 1);
assert_eq!(digit_count(1_000_000), 7);

Prefer no branch? checked_ilog10 returns None for zero instead of panicking, so you can fold the special case into one expression:

1
2
3
let count = |n: u32| n.checked_ilog10().map_or(1, |d| d + 1);
assert_eq!(count(0), 1);
assert_eq!(count(99), 2);

There’s also ilog2 when you want a power-of-two magnitude — the index of the highest set bit:

1
2
assert_eq!(255u32.ilog2(), 7); // 0b1111_1111, top bit is bit 7
assert_eq!(256u32.ilog2(), 8);

All three (ilog10, ilog2, checked_ilog10) work on every integer type. Skip the string round-trip — the math is right there.

#219 Jun 2026

219. checked_add_signed — Move an Unsigned Index by a Signed Delta, No Cast

You have a usize index and a delta: isize that might be negative. idx + delta won’t even compile, and casting your way around it wraps silently on underflow.

The naive fixes are both wrong in their own way:

1
2
3
4
5
let idx: usize = 3;
let delta: isize = -2;

// let next = idx + delta;          // error: cannot add `isize` to `usize`
let next = (idx as isize + delta) as usize; // compiles, wraps on underflow

That cast dance hides bugs: subtract past zero and you get a gigantic index instead of an error.

checked_add_signed adds a signed offset to an unsigned integer and hands back an OptionNone exactly when the result would underflow below zero or overflow the type:

1
2
3
4
5
let idx: usize = 3;

assert_eq!(idx.checked_add_signed(2),  Some(5));
assert_eq!(idx.checked_add_signed(-2), Some(1));
assert_eq!(idx.checked_add_signed(-4), None);   // would go below 0

So moving a cursor inside bounds becomes one honest expression — no as, no manual if delta < 0 branch:

1
2
3
4
5
6
7
fn move_cursor(pos: usize, delta: isize, len: usize) -> Option<usize> {
    pos.checked_add_signed(delta).filter(|&p| p < len)
}

assert_eq!(move_cursor(2, 1, 5), Some(3));
assert_eq!(move_cursor(0, -1, 5), None); // off the front
assert_eq!(move_cursor(4, 1, 5), None);  // off the back

It’s available on every unsigned type with its matching signed offset (u32 takes i32, usize takes isize, and so on). If you’d rather clamp than reject, the saturating_add_signed sibling pins the result to the type’s bounds instead of returning None. And as of Rust 1.90 the _sub_signed variants round out the set for subtracting a signed amount.

#216 Jun 2026

216. unsigned_abs — i32::MIN Has No Positive Twin, So .abs() Overflows

(-5i32).abs() is fine, but i32::MIN.abs() panics — the magnitude 2147483648 doesn’t fit back into an i32. unsigned_abs returns the magnitude in the unsigned type, where it always fits.

Two’s complement isn’t symmetric. An i32 reaches down to -2147483648 but only up to 2147483647, so the most-negative value has no positive counterpart. That makes abs a landmine on exactly one input:

1
2
3
4
5
let x: i32 = -5;
assert_eq!(x.abs(), 5); // fine

let edge = i32::MIN;
let m = edge.abs(); // panics in debug, wraps to i32::MIN in release

The same trap hides inside the classic (a - b).abs() distance trick, which is why abs_diff exists for differences. But when you have a single signed value and just want its magnitude, the fix is unsigned_abs:

1
2
3
let edge = i32::MIN;

assert_eq!(edge.unsigned_abs(), 2147483648u32);

It returns the unsigned sibling type (i32u32, i8u8, …), which is wide enough to hold the magnitude of every input — including the awkward one — so it can never overflow:

1
2
3
assert_eq!((-5i32).unsigned_abs(), 5u32);
assert_eq!((127i8).unsigned_abs(), 127u8);
assert_eq!(i8::MIN.unsigned_abs(), 128u8); // .abs() would panic here

If you genuinely need a signed result and want to handle the edge instead of ignoring it, checked_abs hands you an Option and saturating_abs clamps to MAX:

1
2
assert_eq!(i32::MIN.checked_abs(), None);
assert_eq!(i32::MIN.saturating_abs(), i32::MAX);

Any time you take the absolute value of a signed integer that could conceivably be MIN — parsed input, a subtraction result, a delta from untrusted data — reach for unsigned_abs instead of abs and the panic simply can’t happen.

#215 Jun 2026

215. next_multiple_of — Round Up to a Multiple Without the +m-1 Dance

Padding a length up to the next multiple of 8? The classic (n + 7) / 8 * 8 works right up until it overflows or you fat-finger the constant. next_multiple_of says exactly what you mean.

The hand-rolled version shows up everywhere alignment matters — buffer sizes, page rounding, table padding:

1
2
3
4
// Easy to get subtly wrong, and `n + m - 1` can overflow near the top of the range
fn round_up(n: usize, m: usize) -> usize {
    (n + m - 1) / m * m
}

Every integer type has next_multiple_of: the smallest value >= self that is a multiple of the argument. Already a multiple? It’s returned unchanged.

1
2
3
4
assert_eq!(13u32.next_multiple_of(8), 16); // round up
assert_eq!(16u32.next_multiple_of(8), 16); // already aligned, untouched
assert_eq!(0u32.next_multiple_of(8), 0);
assert_eq!(23u32.next_multiple_of(10), 30);

When the rounded value would overflow, next_multiple_of panics — but checked_next_multiple_of hands you an Option instead, so you stay in control:

1
2
assert_eq!(250u8.checked_next_multiple_of(8), None); // 256 won't fit in u8
assert_eq!(13u8.checked_next_multiple_of(8), Some(16));

One method, the intent on the page, and no off-by-one waiting to bite you.

#213 Jun 2026

213. abs_diff — The Gap Between Two Numbers, No Underflow, No Overflow

Subtracting two unsigned integers to get their distance is a panic waiting to happen — and the signed (a - b).abs() workaround overflows just as quietly. abs_diff gives you the gap correctly, every time.

The reflex for “how far apart are these two numbers” is a plain subtraction. On unsigned types that underflows the moment the second value is larger:

1
2
3
4
let a: u32 = 10;
let b: u32 = 25;

let gap = a - b; // panics in debug, wraps to 4294967281 in release

So you reach for the defensive guard, branching on which side is bigger:

1
let gap = if a > b { a - b } else { b - a };

The signed version looks safer but isn’t. (a - b).abs() overflows when the two values are far apart, and i32::MIN.abs() panics outright because 2147483648 doesn’t fit in an i32.

Since Rust 1.60, every integer type has abs_diff — order-independent, branch-free, and immune to both traps:

1
2
3
4
5
let a: u32 = 10;
let b: u32 = 25;

assert_eq!(a.abs_diff(b), 15);
assert_eq!(b.abs_diff(a), 15);

On signed integers it returns the unsigned counterpart, which is what makes it total. The distance between the extremes is genuinely larger than i32::MAX, and abs_diff represents it without flinching:

1
2
assert_eq!((-5i32).abs_diff(5), 10u32);
assert_eq!(i32::MIN.abs_diff(i32::MAX), u32::MAX);

That last gap — 4294967295 — can’t be held in an i32 at all, which is exactly why (min - max).abs() was never going to work.

Any time you’re computing a difference and only care about magnitude — pixel distances, clock skew, tolerance checks, diffing counters — reach for abs_diff instead of subtracting and hoping the bigger number is on the left.

210. next_power_of_two — Round Up to a Power of Two Without the Bit-Twiddling

Sizing a buffer or hash table to the next power of two is a classic — and people keep reinventing it with shifts and leading_zeros. The standard library already has it.

You’ve probably seen (or written) the bit-twiddling version, which is easy to get subtly wrong on edge cases like 0 or exact powers:

1
2
3
4
5
6
7
8
9
fn round_up_manual(n: u32) -> u32 {
    let mut v = n - 1;        // underflows when n == 0
    v |= v >> 1;
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;
    v + 1
}

next_power_of_two does exactly this, correctly, for every unsigned integer type:

1
2
3
4
assert_eq!(5u32.next_power_of_two(), 8);
assert_eq!(8u32.next_power_of_two(), 8);   // already a power of two
assert_eq!(1u32.next_power_of_two(), 1);
assert_eq!(0u32.next_power_of_two(), 1);   // the tricky one, handled

A common use is rounding an allocation up so masking can replace modulo:

1
2
3
4
5
6
7
let requested = 100usize;
let cap = requested.next_power_of_two();   // 128
assert_eq!(cap, 128);

// because cap is a power of two, idx % cap == idx & (cap - 1)
let idx = 1234usize;
assert_eq!(idx % cap, idx & (cap - 1));

The one trap: if the next power of two doesn’t fit in the type, next_power_of_two panics in debug and wraps to 0 in release. When the input is untrusted, reach for checked_next_power_of_two, which hands you an Option instead:

1
2
3
assert_eq!(200u8.next_power_of_two(), 0);          // wraps in release — bug waiting to happen
assert_eq!(200u8.checked_next_power_of_two(), None); // explicit, safe
assert_eq!(100u8.checked_next_power_of_two(), Some(128));
#132 May 2026

132. abs_diff — Subtract Without Caring Which Side Is Bigger

Subtracting two unsigned integers and the smaller one comes first? Instant panic. a.abs_diff(b) returns the gap as a u* regardless of which side is bigger — no branching, no overflow.

The Problem

Unsigned subtraction in Rust panics in debug and wraps in release the moment the result would go negative. You end up writing the same branch over and over:

1
2
3
4
5
6
fn gap(a: u32, b: u32) -> u32 {
    if a > b { a - b } else { b - a }
}

assert_eq!(gap(10, 3), 7);
assert_eq!(gap(3, 10), 7);

It works, but it’s noise. And the same trick on signed integers has a sneakier bug: i32::MIN.abs_diff(i32::MAX) overflows an i32 — the gap doesn’t fit in the signed range.

After: abs_diff

Every integer type carries an abs_diff method that returns the unsigned gap directly. Signed inputs come back as the matching unsigned type, so the result always fits:

1
2
3
4
5
6
assert_eq!(10u32.abs_diff(3), 7);
assert_eq!(3u32.abs_diff(10), 7);

// Signed → unsigned, no overflow at the extremes
assert_eq!((-5i32).abs_diff(5), 10u32);
assert_eq!(i32::MIN.abs_diff(i32::MAX), u32::MAX);

No if, no checked_sub, no casting through i64 to dodge overflow. One call, one number.

Where It Earns Its Keep

Distance-style calculations are the obvious fit — anywhere “how far apart are these” is the real question and the sign is incidental:

1
2
3
4
5
6
fn manhattan(a: (i32, i32), b: (i32, i32)) -> u32 {
    a.0.abs_diff(b.0) + a.1.abs_diff(b.1)
}

assert_eq!(manhattan((1, 2), (4, 6)), 7);
assert_eq!(manhattan((-3, -3), (3, 3)), 12);

It also cleans up timestamp deltas, where one side is “now” and the other could be in the past or the future:

1
2
3
4
5
let scheduled: u64 = 1_700_000_000;
let actual:    u64 = 1_699_999_995;

let drift = scheduled.abs_diff(actual);
assert_eq!(drift, 5);

Whenever you catch yourself writing if a > b { a - b } else { b - a }, reach for abs_diff instead.

#121 May 2026

121. rem_euclid — The Modulo That Doesn't Go Negative

-1 % 7 in Rust is -1, not 6. That’s a math gotcha lurking in every wraparound index, every clock arithmetic, every “what day of the week” calculation. rem_euclid is the modulo you actually wanted.

Rust’s % operator follows the same rule as C: the sign of the result matches the sign of the dividend. Useful sometimes, surprising the rest of the time:

1
2
3
assert_eq!(-1_i32 % 7, -1);
assert_eq!(-8_i32 % 7, -1);
assert_eq!( 8_i32 % 7,  1);

Try indexing a circular buffer with that and you get a panic the first time you step backwards across zero. The fix is rem_euclid, which always returns a value in [0, |divisor|):

1
2
3
assert_eq!((-1_i32).rem_euclid(7), 6);
assert_eq!((-8_i32).rem_euclid(7), 6);
assert_eq!(( 8_i32).rem_euclid(7), 1);

A real-world shape — wrap an index around a slice in either direction, no if ladder, no manual + len trick:

1
2
3
4
5
6
7
8
9
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];

fn day_after(today: i32, delta: i32) -> i32 {
    (today + delta).rem_euclid(7)
}

assert_eq!(days[day_after(0, -1) as usize], "Sun"); // Mon - 1 = Sun
assert_eq!(days[day_after(2,  4) as usize], "Sun"); // Wed + 4 = Sun
assert_eq!(days[day_after(0, -8) as usize], "Sun"); // wraps past week boundary

div_euclid is the partner that pairs with it: a.div_euclid(b) * b + a.rem_euclid(b) == a always holds, even for negative a. Plain / and % only satisfy that identity for non-negative inputs.

1
2
3
let a = -7_i32;
let b =  3_i32;
assert_eq!(a.div_euclid(b) * b + a.rem_euclid(b), a);

Both are available on every signed integer type (and floats), and they’re const. The rule of thumb: if your code can ever see a negative operand and you want the mathematician’s modulo — not the hardware’s — reach for rem_euclid.

85. cast_signed & cast_unsigned — Explicit Sign Casting for Integers

Stop using as to flip between signed and unsigned integers — cast_signed() and cast_unsigned() say exactly what you mean.

The problem with as

When you write value as u32 or value as i64, the as keyword does too many things at once: it can change the sign, widen, truncate, or even convert floats. Readers have to mentally verify which conversion is actually happening.

1
2
let x: i32 = -1;
let y = x as u32;  // Sign cast? Truncation? Widening? All of the above?

The fix: cast_signed() and cast_unsigned()

Stabilized in Rust 1.87, these methods only reinterpret the sign of an integer — same bit width, same bits, just a different type. If you accidentally try to change the size, it won’t compile.

1
2
3
4
5
6
let signed: i32 = -1;
let unsigned: u32 = signed.cast_unsigned();
assert_eq!(unsigned, u32::MAX); // Same bits, different interpretation

let back: i32 = unsigned.cast_signed();
assert_eq!(back, -1); // Round-trips perfectly

The key constraint: these methods only exist between same-sized pairs (i32u32, i64u64, etc.). There’s no i32::cast_unsigned() returning a u64 — that would silently widen, which is exactly the kind of ambiguity these methods eliminate.

Where this shines

Bit manipulation is the classic use case. When you need to treat an unsigned value as signed for arithmetic and then go back, the intent is crystal clear:

1
2
3
4
5
6
fn wrapping_distance(a: u32, b: u32) -> i32 {
    a.wrapping_sub(b).cast_signed()
}

assert_eq!(wrapping_distance(10, 3), 7);
assert_eq!(wrapping_distance(3, 10), -7);

Compare that to the as version — a.wrapping_sub(b) as i32 — and you can see why reviewers love the explicit method. It’s one less thing to second-guess in a code review.

#082 Apr 2026

82. isqrt — Integer Square Root Without Floating Point

(n as f64).sqrt() as u64 is the classic hack — and it silently gives the wrong answer for large values. Rust 1.84 stabilized isqrt on every integer type: exact, float-free, no precision traps.

The floating-point trap

Converting to f64, calling .sqrt(), and casting back is the go-to pattern. It looks fine. It isn’t.

1
2
3
4
let n: u64 = 10_000_000_000_000_000_000;
let bad = (n as f64).sqrt() as u64;
// bad == 3_162_277_660, but floor(sqrt(n)) is 3_162_277_660 — or is it?
// For many large u64 values, the f64 round-trip is off by 1.

f64 only has 53 bits of mantissa, so for u64 values above 2^53 the conversion loses precision before you even take the square root.

The fix: isqrt

1
2
3
4
let n: u64 = 10_000_000_000_000_000_000;
let root = n.isqrt();
assert_eq!(root * root <= n, true);
assert_eq!((root + 1).checked_mul(root + 1).map_or(true, |sq| sq > n), true);

It’s defined on every integer type — u8, u16, u32, u64, u128, usize, and their signed counterparts — and always returns the exact floor of the square root. No casts, no rounding, no surprises.

Signed integers too

1
2
3
4
5
6
let x: i32 = 42;
assert_eq!(x.isqrt(), 6); // 6*6 = 36, 7*7 = 49

// Negative values would panic, so check first:
let maybe_neg: i32 = -4;
assert_eq!(maybe_neg.checked_isqrt(), None);

Use checked_isqrt on signed types when the input might be negative — it returns Option<T> instead of panicking.

When you’d reach for it

Perfect-square checks, tight loops over divisors, hash table sizing, geometry on integer grids — anywhere you were reaching for f64::sqrt purely to round down, reach for isqrt instead. It’s faster, exact, and one character shorter.

81. checked_sub_signed — Subtract a Signed Delta From an Unsigned Without Casts

checked_add_signed has been around for years. Its missing sibling finally landed: as of Rust 1.91, u64::checked_sub_signed (and the whole {checked, overflowing, saturating, wrapping}_sub_signed family) lets you subtract an i64 from a u64 without casting, unsafe, or hand-rolled overflow checks.

The problem

You’ve got an unsigned counter — a file offset, a buffer index, a frame number — and you want to apply a signed delta. The delta is negative, so subtracting it should increase the counter. But Rust won’t let you subtract an i64 from a u64:

1
2
3
4
5
let pos: u64 = 100;
let delta: i64 = -5;

// error[E0277]: cannot subtract `i64` from `u64`
// let new_pos = pos - delta;

The usual workarounds are all awkward. Cast to i64 and hope nothing overflows. Branch on the sign of the delta and call either checked_sub or checked_add depending. Convert via as and pray.

The fix

checked_sub_signed takes an i64 directly and returns Option<u64>:

1
2
3
4
5
let pos: u64 = 100;

assert_eq!(pos.checked_sub_signed(30),  Some(70));   // normal subtraction
assert_eq!(pos.checked_sub_signed(-5),  Some(105));  // subtracting negative adds
assert_eq!(pos.checked_sub_signed(200), None);       // underflow → None

Subtracting a negative number “wraps around” to addition, exactly as the math says it should. Underflow (going below zero) returns None instead of panicking or silently wrapping.

The whole family

Pick your overflow semantics, same as every other integer op:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let pos: u64 = 10;

// Checked: returns Option.
assert_eq!(pos.checked_sub_signed(-5),  Some(15));
assert_eq!(pos.checked_sub_signed(100), None);

// Saturating: clamps to 0 or u64::MAX.
assert_eq!(pos.saturating_sub_signed(100), 0);
assert_eq!((u64::MAX - 5).saturating_sub_signed(-100), u64::MAX);

// Wrapping: modular arithmetic, never panics.
assert_eq!(pos.wrapping_sub_signed(20), u64::MAX - 9);

// Overflowing: returns (value, did_overflow).
assert_eq!(pos.overflowing_sub_signed(20), (u64::MAX - 9, true));
assert_eq!(pos.overflowing_sub_signed(5),  (5, false));

Same convention as checked_sub, saturating_sub, etc. — you already know the shape.

Why it matters

The signed-from-unsigned case comes up more than you’d think. Scrubbing back and forth in a timeline. Applying a velocity to a position. Rebasing a byte offset. Any time the delta can be negative, you need this method — and now you have it without touching as.

It pairs nicely with its long-stable sibling checked_add_signed, which has been around since Rust 1.66. Between the two, signed deltas on unsigned counters are a one-liner in any direction.

Available on every unsigned primitive (u8, u16, u32, u64, u128, usize) as of Rust 1.91.

#078 Apr 2026

78. div_ceil — Divide and Round Up Without the Overflow Bug

Need to split items into fixed-size pages or chunks? The classic (n + size - 1) / size trick silently overflows. div_ceil does it correctly.

The classic footgun

Paging, chunking, allocating — any time you divide and need to round up, this pattern shows up:

1
2
3
fn pages_needed(total: u64, per_page: u64) -> u64 {
    (total + per_page - 1) / per_page // ⚠️ overflows when total is large
}

It works until total + per_page - 1 wraps around. With u64::MAX items and a page size of 10, you get a wrong answer instead of a panic or correct result.

The fix: div_ceil

Stabilized in Rust 1.73, div_ceil handles the rounding without intermediate overflow:

1
2
3
fn pages_needed(total: u64, per_page: u64) -> u64 {
    total.div_ceil(per_page)
}

One method call, no overflow risk, intent crystal clear.

Real-world examples

Allocating pixel rows for a tiled renderer:

1
2
3
4
let image_height: u32 = 1080;
let tile_size: u32 = 64;
let tile_rows = image_height.div_ceil(tile_size);
assert_eq!(tile_rows, 17); // 16 full tiles + 1 partial

Splitting work across threads:

1
2
3
4
let items: usize = 1000;
let threads: usize = 6;
let chunk_size = items.div_ceil(threads);
assert_eq!(chunk_size, 167); // each thread handles at most 167 items

It works on all unsigned integers

div_ceil is available on u8, u16, u32, u64, u128, and usize. Signed integers also have it (since Rust 1.73), but watch out — it rounds toward positive infinity, which for negative dividends means rounding away from zero.

1
2
let signed: i32 = -7;
assert_eq!(signed.div_ceil(2), -3); // rounds toward +∞, not toward 0

Next time you reach for (a + b - 1) / b, stop — div_ceil already exists and it won’t betray you at the boundaries.