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

#245 Jul 2026

245. rem_euclid — The Modulo That Never Goes Negative

-1 % 5 is -1 in Rust, not 4% keeps the sign of the dividend. When you want a true wrap-around index or clock value, (-1i32).rem_euclid(5) gives you the 4 you actually meant.

Why % bites you

Rust’s % is a remainder, not a mathematical modulo. Its result takes the sign of the left operand:

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

That’s fine for the positive case, but the moment a negative number sneaks in — a decrementing index, an offset that can go below zero — you get a negative result. Feed that into arr[idx as usize] and you either panic or cast into a giant number.

rem_euclid always lands in [0, n)

1
2
3
assert_eq!((-1i32).rem_euclid(5), 4);
assert_eq!((-8i32).rem_euclid(3), 1);
assert_eq!(7i32.rem_euclid(5), 2);

For a positive divisor, rem_euclid is guaranteed to return a non-negative result strictly less than the divisor — exactly what “wrap around” should mean.

Where it pays off: wrapping an index

Stepping backwards through a ring buffer is the textbook case. With % you’d have to add the length back in by hand to avoid going negative:

1
2
3
4
5
6
7
8
9
let buf = ['a', 'b', 'c', 'd'];
let len = buf.len() as i32;

// Move one step left from index 0, wrapping to the end
let mut idx = 0i32;
idx = (idx - 1).rem_euclid(len);

assert_eq!(idx, 3);
assert_eq!(buf[idx as usize], 'd');

No + len fixup, no branch on “did it go negative.” The same trick handles clock arithmetic ((hour + delta).rem_euclid(24)) and angle normalization.

div_euclid is its partner: (-8).div_euclid(3) == -3, and the identity a == b * a.div_euclid(b) + a.rem_euclid(b) always holds. Whenever you catch yourself writing ((x % n) + n) % n to force a positive remainder, that’s rem_euclid spelled the hard way.

#244 Jul 2026

244. clone_from — Reuse the Buffer You Already Have Instead of Reallocating

*dst = src.clone() throws away dst’s heap buffer and allocates a brand-new one every time. dst.clone_from(&src) copies into the storage dst already owns — reusing its capacity instead of freeing and re-grabbing it.

The hidden allocation in assignment

Cloning into an existing variable looks free, but it isn’t:

1
2
3
4
5
6
7
8
9
let mut dst = String::with_capacity(64);
dst.push_str("previous value");

let src = String::from("new value");

// Drops dst's 64-byte buffer, allocates a fresh one for src's length
dst = src.clone();

assert_eq!(dst, "new value");

src.clone() builds a completely new String with its own allocation, then the assignment drops the old dst — buffer and all. In a loop that runs thousands of times, that’s a free-then-allocate churn on every iteration, even when the old buffer was plenty big.

clone_from copies into place

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let mut dst = String::with_capacity(64);
dst.push_str("previous value");

let src = String::from("new value");

// Overwrites dst's existing buffer; keeps the capacity if it fits
dst.clone_from(&src);

assert_eq!(dst, "new value");
assert!(dst.capacity() >= 64); // same allocation, reused

Clone::clone_from is a provided trait method whose whole job is “make self equal to source, reusing self’s resources where possible.” For String and Vec, that means copying the bytes into the buffer that’s already there and only reallocating if it’s too small. The default impl just does *self = source.clone(), but the collections override it to reuse storage.

Where it pays off: the reused accumulator

The classic win is a buffer you refresh every pass through a loop — pair it with the reuse-one-buffer pattern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let inputs = vec![vec![1, 2, 3], vec![4, 5, 6, 7], vec![8]];
let mut scratch: Vec<i32> = Vec::new();

for row in &inputs {
    scratch.clone_from(row); // reuses scratch's capacity each time
    scratch.iter_mut().for_each(|x| *x *= 10);
    // ... use scratch ...
}

assert_eq!(scratch, vec![80]); // last row, scaled

Every clone_from after the first reuses whatever capacity scratch grew to, so the loop stops hammering the allocator. Swap in scratch = row.clone() and you’re back to a fresh allocation on each turn.

It works for any nested owned type too — a Vec<String> clones each element with clone_from, so inner buffers get reused, not rebuilt. Whenever you catch yourself writing dst = src.clone() for a dst you already own, clone_from is the version that doesn’t throw the buffer away.

#243 Jul 2026

243. slice::as_flattened — Treat a Slice of Arrays as One Flat Slice, No Copy

You’ve got a Vec<[f32; 3]> of RGB pixels and an API that wants &[f32]. The manual flatten allocates a whole second buffer. as_flattened hands you the same bytes as a flat &[f32] — zero copies, zero allocation.

The copy you didn’t need

A slice of fixed-size arrays is already contiguous in memory. But reach for a flat view the obvious way and you rebuild it element by element into a fresh Vec:

1
2
3
4
5
6
let pixels: Vec<[f32; 3]> = vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];

// Rebuilds every value into a new allocation
let flat: Vec<f32> = pixels.iter().flatten().copied().collect();

assert_eq!(flat, vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0]);

That collect walks all six values and heap-allocates a second buffer — pure waste when the layout you want is already sitting there.

as_flattened is a free reinterpretation

1
2
3
4
5
6
let pixels: Vec<[f32; 3]> = vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];

let flat: &[f32] = pixels.as_flattened();

assert_eq!(flat, &[1.0, 0.0, 0.0, 0.0, 1.0, 0.0]);
assert_eq!(flat.len(), 6); // outer_len * N

<[[T; N]]>::as_flattened takes &[[T; N]] and returns &[T] covering the exact same memory. No copy, no allocation — just a pointer and a length. The result borrows the original, so it stays as cheap as it looks.

Mutate through the flat view

as_flattened_mut gives you &mut [T], so you can run a flat transform over structured data without unpacking it:

1
2
3
4
5
6
7
let mut rows = [[1, 2, 3], [4, 5, 6]];

for v in rows.as_flattened_mut() {
    *v *= 10;
}

assert_eq!(rows, [[10, 20, 30], [40, 50, 60]]);

Same storage, edited in place — the array grouping is still there when you’re done.

Where it shines: handing structured data to flat APIs

Vertex buffers, audio frames, matrices — anything you model as [T; N] but a lower-level API wants as one long run:

1
2
3
4
5
6
7
8
9
// A 2x3 matrix stored as rows
let matrix = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];

fn dot(a: &[f64], b: &[f64]) -> f64 {
    a.iter().zip(b).map(|(x, y)| x * y).sum()
}

let flat = matrix.as_flattened();
assert_eq!(dot(flat, flat), 91.0); // 1+4+9+16+25+36

You keep the readable [[f64; 3]; 2] shape in your own code and pass a &[f64] across the boundary — no glue buffer in between. Whenever you catch yourself flatten().collect()-ing a slice of arrays just to change its type, as_flattened is the zero-cost version.

#242 Jul 2026

242. slice::binary_search_by_key — Find a Record by One Field, No Hand-Written Comparator

Binary searching a slice of structs by one field? Don’t hand-roll a .cmp() closure and risk flipping the comparison — project the key and let the stdlib do the rest.

You have a slice sorted by some field and want to find an element by that field. The reflex is binary_search_by with a closure that spells out the comparison — and it’s easy to get the argument order backwards, which silently breaks the search:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#[derive(Debug, PartialEq)]
struct Employee {
    id: u32,
    name: &'static str,
}

let staff = [
    Employee { id: 3,  name: "Ada" },
    Employee { id: 7,  name: "Bo"  },
    Employee { id: 12, name: "Cy"  },
];

// The awkward way — you write (and can mis-order) the comparator
let idx = staff.binary_search_by(|e| e.id.cmp(&7));
assert_eq!(idx, Ok(1));

binary_search_by_key takes the target and a closure that projects the key. No .cmp(), nothing to get backwards:

1
2
3
let idx = staff.binary_search_by_key(&7, |e| e.id);
assert_eq!(idx, Ok(1));
assert_eq!(staff[idx.unwrap()].name, "Bo");

On a miss you get Err(i) — the index where the element would go to keep the slice sorted, so you can insert without a second search:

1
2
3
4
match staff.binary_search_by_key(&10, |e| e.id) {
    Ok(i)  => println!("found at {i}"),
    Err(i) => println!("would insert at {i}"), // Err(2)
}

One rule: the slice must already be sorted by the same key you project, otherwise the result is unspecified. When that holds, it’s O(log n) instead of the O(n) scan you’d write by hand.

#241 Jul 2026

241. slice::split_first — Peel the Head Off a Slice, Keep the Tail, No Indexing

slice[0] panics on an empty slice, and &slice[1..] is a second chance to get the bounds wrong. split_first hands you the head and the tail together — or None if there’s nothing there — so the empty case is a pattern, not a panic.

The manual head-and-tail

Reach for the first element and the rest, and you write two indexing operations that both assume the slice is non-empty:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn describe(items: &[&str]) -> String {
    if items.is_empty() {
        return "nothing".to_string();
    }
    let first = items[0];      // panics if you forget the guard above
    let rest = &items[1..];    // and so does this
    format!("{first} plus {} more", rest.len())
}

assert_eq!(describe(&["a", "b", "c"]), "a plus 2 more");

The bounds check is real, but it’s on you to remember the is_empty guard. Drop it and an empty slice panics at runtime.

split_first gives you both, safely

1
2
3
4
5
6
7
8
9
fn describe(items: &[&str]) -> String {
    match items.split_first() {
        Some((first, rest)) => format!("{first} plus {} more", rest.len()),
        None => "nothing".to_string(),
    }
}

assert_eq!(describe(&["a", "b", "c"]), "a plus 2 more");
assert_eq!(describe(&[]), "nothing"); // no panic — the None arm handles it

split_first returns Option<(&T, &[T])>: the first element and a slice of everything after it, or None when the slice is empty. The empty case can’t be forgotten — it’s a variant you have to match.

split_last peels from the other end

1
2
3
4
5
6
let path = ["usr", "local", "bin"];

if let Some((last, parents)) = path.split_last() {
    assert_eq!(*last, "bin");
    assert_eq!(parents, &["usr", "local"]);
}

Same shape, mirrored: the last element plus everything before it.

Where it shines: recursion without index math

Because the tail is just another slice, split_first makes structural recursion clean — the base case is None, and there’s no i + 1 to fumble:

1
2
3
4
5
6
7
8
9
fn sum(slice: &[i32]) -> i32 {
    match slice.split_first() {
        Some((head, tail)) => head + sum(tail),
        None => 0,
    }
}

assert_eq!(sum(&[1, 2, 3, 4]), 10);
assert_eq!(sum(&[]), 0);

Need to mutate as you walk? split_first_mut and split_last_mut return (&mut T, &mut [T]), so you can edit the head and recurse into the tail without a borrow fight. Whenever you catch yourself pairing slice[0] with &slice[1..], this is the method that folds both — and the empty check — into one.

#240 Jul 2026

240. to_le_bytes / from_le_bytes — Serialize Integers Without unsafe or Bit-Shifting

Packing a u32 into four bytes by hand means a stack of >> and as u8 casts — and one wrong shift silently corrupts your data. Every integer type already knows how to lay itself out, in the exact endianness you ask for.

The hand-rolled shift-and-mask

Serializing an integer to bytes the manual way is easy to get subtly wrong:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let n: u32 = 300;

// big-endian by hand: shift, cast, repeat
let bytes = [
    (n >> 24) as u8,
    (n >> 16) as u8,
    (n >> 8) as u8,
    n as u8,
];
assert_eq!(bytes, [0, 0, 1, 44]);

It works, but the shift amounts are magic numbers, the byte order is implicit, and reversing it (parsing bytes back into a u32) means another shift ladder in the opposite direction.

to_*_bytes hands you the array

Every integer type has to_be_bytes, to_le_bytes, and to_ne_bytes, each returning a fixed-size [u8; N]:

1
2
3
4
let n: u32 = 0x12345678;

assert_eq!(n.to_be_bytes(), [0x12, 0x34, 0x56, 0x78]); // big-endian
assert_eq!(n.to_le_bytes(), [0x78, 0x56, 0x34, 0x12]); // little-endian

The endianness is spelled out in the method name, the array length is checked at compile time ([u8; 4] for a u32), and there’s no cast to fumble.

from_*_bytes parses it back

The inverse takes the same fixed-size array and rebuilds the integer:

1
2
3
4
let n: u32 = 300;
let wire = n.to_be_bytes();

assert_eq!(u32::from_be_bytes(wire), 300); // exact round-trip

Reading an integer out of a buffer

The array size is part of the type, so slice a &[u8] and try_into an array before parsing — the length check happens for you:

1
2
3
4
let buf = [0x00, 0x00, 0x01, 0x2c, 0xff]; // 4-byte int + trailing byte

let n = u32::from_be_bytes(buf[..4].try_into().unwrap());
assert_eq!(n, 300);

If the slice is the wrong length, try_into returns an Err instead of reading past the end — no unsafe, no out-of-bounds read.

Use to_be_bytes / from_be_bytes for network and file formats (big-endian is the usual “wire” order), _le_ when a spec demands little-endian, and reach for _ne_ (native) only for in-memory blobs that never leave the machine. Floats have the same methods, and to_bits / from_bits if you want the raw u32/u64 first.