Integers

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.