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

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