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

#251 Jul 2026

251. include_str! — Ship the File Inside the Binary, Skip the Runtime Read

A missing template or SQL file in the deploy takes your app down at startup. include_str! bakes the file into the binary at compile time — a missing file becomes a compile error, not a production incident.

The runtime way — and its failure mode

1
2
3
// Runs at startup: I/O, error handling, and the
// file must be shipped alongside the executable.
let template = std::fs::read_to_string("greeting.txt")?;

This works until someone forgets to copy greeting.txt into the container, or the working directory isn’t what you assumed. The failure shows up at runtime, on someone else’s machine.

The compile-time way

1
2
// Embedded in the executable at compile time.
static TEMPLATE: &str = include_str!("greeting.txt");

The file’s contents become a &'static str inside your binary. No I/O, no Result, nothing to deploy alongside. If the file is missing or isn’t valid UTF-8, cargo build fails — the mistake never leaves your machine.

For binary assets there is include_bytes!, which gives you a &'static [u8]:

1
static LOGO: &[u8] = include_bytes!("logo.png");

Path gotcha

The path is relative to the current source file, not the crate root or working directory. For paths that survive refactoring into submodules, anchor them to the manifest:

1
2
3
static QUERY: &str = include_str!(
    concat!(env!("CARGO_MANIFEST_DIR"), "/queries/get_user.sql")
);

When to reach for it

SQL queries, HTML templates, license text, shader source, test fixtures — anything small and fixed at build time. Skip it for files that must be user-editable after deployment, or big enough to bloat the binary noticeably: embedding means every change requires a recompile. That’s the trade — and for config that should never drift from the code, it’s exactly what you want.

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

#247 Jul 2026

247. sort_unstable — Skip the Allocation When Ties Don't Matter

.sort() pays for a guarantee you usually don’t need: it allocates a scratch buffer to keep equal elements in their original order. .sort_unstable() sorts in place — no allocation, and typically faster.

What “stable” actually buys you

A stable sort preserves the relative order of elements that compare equal. To do that, slice::sort allocates temporary storage proportional to the slice length. slice::sort_unstable works entirely in place and is generally the faster of the two — the docs themselves suggest preferring it when stability isn’t required:

1
2
3
let mut nums = [5, 3, 9, 1, 3];
nums.sort_unstable();
assert_eq!(nums, [1, 3, 3, 5, 9]);

For integers, floats-via-total_cmp, or any type where two equal elements are indistinguishable, stability buys you nothing — there is no observable “original order” among identical values. That’s most sorts you’ll ever write.

When you actually need .sort()

Stability matters when elements carry more data than the sort key, and the order of ties is meaningful. Classic case: re-sorting an already-sorted list by a second criterion:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Already sorted by name.
let mut employees = vec![
    ("alice", 2), ("bob", 1), ("carol", 2),
];

// Stable sort by department: names stay
// alphabetical within each department.
employees.sort_by_key(|&(_, dept)| dept);
assert_eq!(employees, [
    ("bob", 1), ("alice", 2), ("carol", 2),
]);

With sort_unstable_by_key, alice and carol (both dept 2) could end up in either order — the assert above would be a coin flip.

The unstable variants

Everything has an unstable twin: sort_unstable, sort_unstable_by, and sort_unstable_by_key:

1
2
3
let mut words = vec!["hello", "hi", "hey"];
words.sort_unstable_by_key(|w| w.len());
assert_eq!(words, ["hi", "hey", "hello"]);

Note one asymmetry: sort_by_key with an expensive key function has a cached cousin (sort_by_cached_key, bite 98), but there’s no sort_unstable_by_cached_key — caching needs the same kind of scratch allocation that unstable sorting exists to avoid.

Rule of thumb: reach for sort_unstable by default; upgrade to sort only when a tie-breaking order among equal elements is part of your program’s meaning.

#246 Jul 2026

246. Iterator::peekable — Look at the Next Item Without Consuming It

Sometimes you need to see the next element to decide what to do — but calling .next() eats it. .peekable() gives you a .peek() that shows the next item while leaving it in place.

The problem: deciding based on what comes next

A classic case is joining items with a separator. You want a comma between elements but not a trailing one, so you need to know “is there another item after this?” A plain iterator can’t tell you without consuming it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn join_csv(items: &[&str]) -> String {
    let mut out = String::new();
    let mut it = items.iter().peekable();
    while let Some(item) = it.next() {
        out.push_str(item);
        if it.peek().is_some() {
            out.push_str(", ");
        }
    }
    out
}

assert_eq!(join_csv(&["a", "b", "c"]), "a, b, c");
assert_eq!(join_csv(&["solo"]), "solo");

peek() returns Option<&Item> — a reference to the next value if there is one — without advancing the iterator. The next .next() still hands you that same element.

The real power: peek to decide, then consume

Peeking shines when you’re parsing a stream and want to grab a run of elements that match a condition. Look at the front, and only call .next() once you’ve decided to keep it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::iter::Peekable;
use std::str::Chars;

// Read a run of digits off the front of a char stream.
fn take_number(chars: &mut Peekable<Chars>) -> u32 {
    let mut n = 0;
    while let Some(&c) = chars.peek() {
        match c.to_digit(10) {
            Some(d) => {
                n = n * 10 + d;
                chars.next(); // commit: actually consume it
            }
            None => break, // leave the non-digit in place
        }
    }
    n
}

let mut chars = "42px".chars().peekable();
assert_eq!(take_number(&mut chars), 42);
// The "px" is untouched, ready for the next parser.
assert_eq!(chars.collect::<String>(), "px");

The non-digit p stays in the iterator because we peeked at it instead of consuming it — the caller picks up exactly where the number ended.

next_if for the common case

When the pattern is “consume the next item only if it matches,” next_if does the peek-and-maybe-advance in one call:

1
2
3
4
let mut it = [1, 2, 3].iter().peekable();
assert_eq!(it.next_if(|&&x| x == 1), Some(&1)); // matches, consumed
assert_eq!(it.next_if(|&&x| x == 99), None);    // no match, 2 stays put
assert_eq!(it.next(), Some(&2));

There’s also next_if_eq for the “advance past this exact value” case. Whenever you find yourself wishing you could un-call .next(), reach for .peekable().