Std

151. Cell<T> — Interior Mutability Without the Borrow Checker Drama

You hand the same struct to two closures and both want to bump a counter. &mut fights you, RefCell introduces runtime borrow checks you don’t need — Cell<T> quietly mutates through a shared & reference, with zero overhead, as long as you only ever swap whole values in and out.

The pain: shared & and a counter

Two closures, one counter, one immutable reference:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct Counter {
    hits: u32,
}

let counter = Counter { hits: 0 };
let bump = || {
    // ERROR: cannot assign to `counter.hits`, which is behind a `&` reference
    // counter.hits += 1;
};
bump();

Fn closures only capture &, so a plain field is read-only. You could redesign for FnMut and a single owner, but the moment you have two observers or a callback registry, you need interior mutability.

The fix: Cell<T> mutates through &

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::cell::Cell;

struct Counter {
    hits: Cell<u32>,
}

let counter = Counter { hits: Cell::new(0) };

let bump = || counter.hits.set(counter.hits.get() + 1);
let read = || counter.hits.get();

bump();
bump();
bump();
assert_eq!(read(), 3);

Cell::new wraps the value; get returns a copy (so T: Copy for that method); set overwrites. Both take &self — no &mut anywhere — which is why this works inside Fn closures, inside Rc, inside any structure that hands out shared references.

The invariant: you can never borrow the inside

This is the single rule that makes Cell<T> sound without runtime checks: you cannot get a reference to the value inside, only copies and swaps. There is no cell.as_ref(), no cell.deref(). The compiler enforces this — there’s nothing to alias, so the optimizer is free to assume the inner value can’t change underneath an outstanding &T.

That’s also why Cell<T> is !Sync — fine for one thread, but two threads racing on set would tear the value. For threads, reach for Atomic* (scalars) or Mutex<T> (the rest).

replace and take work for non-Copy types too

get requires T: Copy, but Cell works with String, Vec, anything — you just have to move the value out instead of copying it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::cell::Cell;

let slot: Cell<String> = Cell::new(String::from("hello"));

// `replace` swaps in a new value, returns the old one.
let old = slot.replace(String::from("world"));
assert_eq!(old, "hello");

// `take` is `replace(Default::default())`.
let now: String = slot.take();
assert_eq!(now, "world");
assert_eq!(slot.into_inner(), ""); // default String

This is the trick people miss: Cell<Vec<T>> is perfectly usable for a shared, append-only-ish buffer — you just swap the whole Vec in and out.

When to pick Cell vs RefCell

Cell<T> if you only need to swap whole values: counters, flags, configuration knobs, small Copy state, or any field where replace/take is enough. Zero runtime overhead, no panic risk.

RefCell<T> (tomorrow afternoon’s bite) if you need to borrow the inside — call &mut self methods on a Vec in place, hand a &str slice to a caller, anything where copying or swapping the whole value would be wrong. You pay for runtime borrow tracking and risk a panic, but you get back the ability to use the value normally.

Default to Cell when it fits — it almost always does for simple shared-state tweaks, and the “no inner references” rule turns out to be exactly what you wanted anyway.

#150 May 2026

150. Vec::spare_capacity_mut — Fill a Vec From a Callback Without Zeroing It First

You reserve 4 KiB to read from a socket, and to hand the buffer over you first… write 4096 zeros. Vec::spare_capacity_mut exposes the reserved-but-uninitialized tail as &mut [MaybeUninit<u8>] so the callback writes straight into the allocation.

The pain: paying to overwrite

The intuitive fill-a-buffer pattern resizes the Vec first so the slice exists:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let mut buf: Vec<u8> = Vec::with_capacity(8);
buf.resize(8, 0); // writes 8 zeros we're about to clobber

// Pretend this is `read(fd, buf.as_mut_ptr(), buf.len())`.
fill(&mut buf);

assert_eq!(buf, b"rustbite");

fn fill(out: &mut [u8]) {
    out.copy_from_slice(b"rustbite");
}

It works, but resize walks the whole tail writing zeros that the next line overwrites — a measurable cost for big reads, and pointless for types where “zero” isn’t even a valid value.

The fix: write into the uninitialized tail

Vec::spare_capacity_mut(&mut self) -> &mut [MaybeUninit<T>] hands you a slice covering exactly capacity - len slots. Write into them, then call set_len to tell the Vec they’re now initialized:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::mem::MaybeUninit;

let mut buf: Vec<u8> = Vec::with_capacity(8);

// View the reserved-but-uninitialized tail.
let spare: &mut [MaybeUninit<u8>] = buf.spare_capacity_mut();
assert_eq!(spare.len(), 8);

// Write through the MaybeUninit pointer — no zeroing first.
for (slot, byte) in spare.iter_mut().zip(b"rustbite") {
    slot.write(*byte);
}

// Promise the Vec those 8 slots are now valid `u8`s.
unsafe { buf.set_len(8); }

assert_eq!(buf, b"rustbite");

spare_capacity_mut itself is safe — MaybeUninit<T> is the type that lets you hold “maybe garbage” without UB. The unsafe block is just the set_len call where you assert you really did initialize them.

Pairing with a real fill API

The standard pattern is “reserve, write, set_len”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use std::mem::MaybeUninit;

fn read_bytes(buf: &mut Vec<u8>, extra: usize, source: &[u8]) {
    buf.reserve(extra);

    let spare = buf.spare_capacity_mut();
    let n = extra.min(source.len()).min(spare.len());

    // Initialize the prefix we actually wrote.
    for i in 0..n {
        spare[i].write(source[i]);
    }

    // Only extend by what's been initialized.
    unsafe { buf.set_len(buf.len() + n); }
}

let mut v = vec![b'>', b' '];
read_bytes(&mut v, 8, b"rustbite");
assert_eq!(v, b"> rustbite");

The same shape works with read-style callbacks: cast the MaybeUninit<u8> slice to a raw pointer, hand it to C, and only extend len by the byte count the call returned. The bytes you didn’t write stay MaybeUninit — never read them.

When to reach for it

Reading from sockets, files, or FFI fill-style APIs into a Vec<u8> is the headline use case — every tokio and mio read path eventually bottoms out in this pattern. It’s also useful for non-Copy types where there’s no sensible default to seed with: image decoders writing Vec<Pixel>, audio decoders writing Vec<f32>, parser arenas writing Vec<Node>.

If you don’t need the spare capacity view — you’re building up element-by-element — Vec::push (or Vec::push_mut from bite 88) is still the right call. spare_capacity_mut is the tool for the moment you have an external writer that wants a flat buffer and you’d rather not pay to zero it first.

147. Cell::as_array_of_cells — Mutate One Slot of a Cell-Wrapped Array

You have a Cell<[i32; 4]> and you want to bump element [2]. cell.get(), mutate the copy, cell.set(...) the whole thing back — for one slot? Cell::as_array_of_cells hands you &[Cell<i32>; 4] so each slot is its own little Cell.

The setup

Cell<T> gives you interior mutability for Copy types: &Cell<T> lets you swap the inner value through a shared reference. That’s lovely for a scalar, but the moment T is an array it becomes awkward — Cell only exposes get() and set() for the entire T:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::cell::Cell;

fn main() {
    let scores = Cell::new([10, 20, 30, 40]);

    // Want to bump index 2. The natural reach...
    // scores[2] += 1;            // can't index through Cell
    // scores.get()[2] += 1;       // mutating a temporary copy — does nothing

    // The "real" old way: copy out, mutate, copy back.
    let mut arr = scores.get();
    arr[2] += 1;
    scores.set(arr);

    assert_eq!(scores.get(), [10, 20, 31, 40]);
}

Three lines and a full-array copy in each direction — just to add 1. It also doesn’t compose: if you wanted to hand a single slot to another function, you’d have to pass the whole Cell<[i32; 4]> plus an index, and trust the callee to put the array back.

Enter as_array_of_cells

Stabilized in Rust 1.91, Cell::as_array_of_cells reinterprets &Cell<[T; N]> as &[Cell<T>; N]:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::cell::Cell;

fn main() {
    let scores = Cell::new([10, 20, 30, 40]);
    let slots: &[Cell<i32>; 4] = scores.as_array_of_cells();

    // Each element is now its own Cell — mutate one without touching the others.
    slots[2].set(slots[2].get() + 1);

    assert_eq!(scores.get(), [10, 20, 31, 40]);
}

No copy, no set of the whole array. The cast is free at runtime — Cell<T> is #[repr(transparent)] over T, so a Cell<[T; N]> and a [Cell<T>; N] have identical layout. The standard library just gives you the safe view of that fact.

Pair it with Cell::update

Cell::update is the obvious dance partner — read-modify-write in one call, on a single slot:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::cell::Cell;

fn main() {
    let scores = Cell::new([10, 20, 30, 40]);

    for slot in scores.as_array_of_cells() {
        slot.update(|n| n * 2);
    }

    assert_eq!(scores.get(), [20, 40, 60, 80]);
}

That’s the loop you actually wanted. No RefCell, no runtime borrow check, no panic risk.

Hand out a single slot

Because each element is a real &Cell<T>, you can pass one slot to another function and let it mutate just that slot — the rest of the array is untouched and the caller keeps full access:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::cell::Cell;

fn bump(slot: &Cell<i32>) {
    slot.update(|n| n + 100);
}

fn main() {
    let scores = Cell::new([10, 20, 30, 40]);
    let slots = scores.as_array_of_cells();

    bump(&slots[1]);
    bump(&slots[3]);

    assert_eq!(scores.get(), [10, 120, 30, 140]);
}

Try expressing that with cell.get() / cell.set() — you can’t, not without rebuilding the array on every call.

Slices too

There’s a sibling for unsized arrays: Cell::as_slice_of_cells turns &Cell<[T]> into &[Cell<T>]. Useful when the length isn’t known at compile time:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::cell::Cell;

fn zero_out(buf: &Cell<[u8]>) {
    for slot in buf.as_slice_of_cells() {
        slot.set(0);
    }
}

fn main() {
    let buf: Cell<[u8; 5]> = Cell::new([1, 2, 3, 4, 5]);
    // &Cell<[u8; 5]> coerces to &Cell<[u8]> at the call site.
    zero_out(&buf);
    assert_eq!(buf.get(), [0, 0, 0, 0, 0]);
}

And as of Rust 1.95, both views also implement AsRef, so generic code can take impl AsRef<[Cell<T>]> and accept either form.

The signatures

1
2
3
4
5
6
7
impl<T, const N: usize> Cell<[T; N]> {
    pub const fn as_array_of_cells(&self) -> &[Cell<T>; N];
}

impl<T> Cell<[T]> {
    pub fn as_slice_of_cells(&self) -> &[Cell<T>];
}

Both are zero-cost reinterpretations — pure type-system moves, no copying. Reach for them any time you find yourself doing the get / mutate / set two-step on a Cell that wraps a collection.

#146 May 2026

146. char::MAX_LEN_UTF8 — Size UTF-8 Buffers Without Magic Numbers

Every time you’ve called char::encode_utf8, you’ve written [0u8; 4] from memory. Rust 1.93 stabilises char::MAX_LEN_UTF8 so you don’t have to keep that magic number in your head.

The magic number you keep typing

encode_utf8 writes the UTF-8 bytes of a char into a &mut [u8] and returns a &mut str pointing at the written portion. The slice has to be big enough — which means knowing that the worst-case UTF-8 encoding is 4 bytes:

1
2
3
let mut buf = [0u8; 4]; // why 4? because UTF-8, that's why
let s = '🦀'.encode_utf8(&mut buf);
assert_eq!(s, "🦀");

That 4 is correct but unexplained. Anyone reading your code has to either trust you or go re-derive the UTF-8 spec.

The named version

Rust 1.93 stabilises two constants on char:

1
2
assert_eq!(char::MAX_LEN_UTF8, 4);
assert_eq!(char::MAX_LEN_UTF16, 2);

MAX_LEN_UTF8 is the maximum number of u8s encode_utf8 can ever write. MAX_LEN_UTF16 is the same for encode_utf16 (a surrogate pair = 2 u16s). Drop them straight into your buffer declarations:

1
2
3
4
5
6
7
8
let mut buf = [0u8; char::MAX_LEN_UTF8];
let s = '🦀'.encode_utf8(&mut buf);
assert_eq!(s, "🦀");
assert_eq!(s.len(), 4);

let mut wide = [0u16; char::MAX_LEN_UTF16];
let w = '🦀'.encode_utf16(&mut wide);
assert_eq!(w.len(), 2);

Same behaviour, but the intent is self-documenting — the buffer is sized to hold exactly one char, by definition.

Sizing a buffer for N chars

Where this really pays off is when you’re computing a buffer for several chars on the stack:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const N: usize = 8;
let mut buf = [0u8; N * char::MAX_LEN_UTF8];

let mut pos = 0;
for c in ['h', 'é', 'l', 'l', 'o'] {
    let s = c.encode_utf8(&mut buf[pos..]);
    pos += s.len();
}

assert_eq!(&buf[..pos], "héllo".as_bytes());

Now if Unicode ever expanded its scalar value range and MAX_LEN_UTF8 grew, your code would still be correct. With a hardcoded 4, you’d have a silent buffer overflow waiting to happen the day someone bumps the constant.

Why bother?

It’s a small change — one constant, no new behaviour. But it kills a real source of off-by-one bugs (people writing [0u8; 3] because they “only handle Latin-1”) and makes UTF-8 buffer code legible at a glance. Available since Rust 1.93 (January 2026).

#145 May 2026

145. Duration::from_nanos_u128 — Round-Trip Nanoseconds Without the u64 Cast

Duration::as_nanos() hands you a u128. Duration::from_nanos() takes a u64. You feed one into the other and the compiler yells at you — or worse, you cast and quietly truncate at 584 years. Rust 1.93 closed the loop with from_nanos_u128.

The mismatched-types papercut

The old API was asymmetric. Going from Duration to nanos was 128-bit:

1
2
3
4
5
use std::time::Duration;

let d = Duration::new(7, 250);
let n: u128 = d.as_nanos();
assert_eq!(n, 7_000_000_250);

Coming back, though, you only got from_nanos(_: u64) — so the round-trip needed a cast:

1
2
3
4
5
use std::time::Duration;

let n: u128 = Duration::new(7, 250).as_nanos();
let back = Duration::from_nanos(n as u64); // narrowing cast, fingers crossed
assert_eq!(back, Duration::new(7, 250));

That as u64 silently truncates anything past u64::MAX — and u64::MAX nanoseconds is roughly 584 years. Inside a calendar app you’ll never notice. Inside a scientific or simulation context, you absolutely will.

from_nanos_u128 matches as_nanos

Rust 1.93 stabilised Duration::from_nanos_u128, a const fn that takes the full 128-bit value:

1
2
3
4
5
use std::time::Duration;

let n: u128 = Duration::new(7, 250).as_nanos();
let back = Duration::from_nanos_u128(n);
assert_eq!(back, Duration::new(7, 250));

Same shape on both sides. No cast, no truncation, no silent wraparound.

Past the 584-year ceiling

Where the new constructor actually earns its keep is when you have nanoseconds counts that wouldn’t fit in a u64:

1
2
3
4
5
6
7
8
9
use std::time::Duration;

// 10^24 ns is ~31.7 million years — well past u64::MAX nanos
let nanos: u128 = 10_u128.pow(24) + 321;
let d = Duration::from_nanos_u128(nanos);

assert_eq!(d.as_secs(), 10_u64.pow(15));
assert_eq!(d.subsec_nanos(), 321);
assert_eq!(d.as_nanos(), nanos); // exact round-trip

Duration itself stores (u64 seconds, u32 nanos), so it has plenty of room — the old from_nanos was just bottlenecked by its argument type.

One thing to watch

from_nanos_u128 panics if you hand it more than Duration::MAX worth of nanoseconds. If you’re pulling values from user input or untrusted sources, guard the upper bound yourself — there isn’t a checked_from_nanos_u128 (yet).

When to reach for it

Use from_nanos_u128 whenever you already have a u128 of nanoseconds — typically because it came out of as_nanos, an arithmetic accumulator, or a high-precision external clock. Stick with the plain from_nanos(_: u64) for short-lived timeouts and durations measured in milliseconds or seconds; the u64 is plenty.

Stabilised in Rust 1.93 (January 2026). Available as const fn, so it works in const contexts too.

#143 May 2026

143. Vec::dedup_by_key — Collapse Consecutive Duplicates by a Derived Key

Vec::dedup() only collapses runs that are exactly equal. When you care about a derived attribute — the minute on a timestamp, the domain in an email, the first letter of a word — reach for dedup_by_key.

The plain dedup() is strict: two adjacent elements are only merged if == says so.

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

But often “duplicate” really means “shares some property with its neighbour.” dedup_by_key takes a closure that maps each element to a key, then keeps the first of every consecutive run whose keys match:

1
2
3
let mut nums = vec![1, 3, 5, 2, 4, 7, 6, 8];
nums.dedup_by_key(|n| *n % 2);
assert_eq!(nums, vec![1, 2, 7, 6]);

1, 3, 5 all have key 1 → keep 1. Then 2, 4 have key 0 → keep 2. Then 7 has key 1 → keep it. Then 6, 8 have key 0 → keep 6.

The practical case: log lines already in time order, and you want one representative per minute.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let mut lines = vec![
    "12:01 GET /a".to_string(),
    "12:01 GET /b".to_string(),
    "12:01 POST /c".to_string(),
    "12:02 GET /d".to_string(),
    "12:02 GET /e".to_string(),
    "12:03 PATCH /f".to_string(),
];

lines.dedup_by_key(|line| line[..5].to_string());

assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "12:01 GET /a");
assert_eq!(lines[1], "12:02 GET /d");
assert_eq!(lines[2], "12:03 PATCH /f");

Two things worth remembering. First, it only looks at adjacent pairs — if you need full uniqueness, pair it with sort_by_key first. Second, if your equivalence isn’t expressible as a key (e.g. “values within 0.1 of each other”), there’s a sibling dedup_by that takes |a, b| -> bool directly. All three are in-place, allocation-free, and run in linear time.

#144 May 2026

144. Vec::into_raw_parts — Hand a Vec to C Without the ManuallyDrop Dance

You want to give a Rust-allocated buffer to C and re-take it later. That means handing over (ptr, len, capacity) — and historically, prying those three out of a Vec without freeing the allocation meant wrapping the vector in ManuallyDrop first. Rust 1.93 stabilises Vec::into_raw_parts, a single safe call that returns the triple and consumes the Vec for you.

The pain: extracting parts while suppressing drop

The classic recipe leaks the Vec’s destructor on purpose so the C side owns the memory. You need three reads and a guard to keep Drop from racing the allocator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::mem::ManuallyDrop;

let v: Vec<u32> = vec![10, 20, 30];

let mut me = ManuallyDrop::new(v);
let ptr = me.as_mut_ptr();
let len = me.len();
let cap = me.capacity();

assert_eq!(unsafe { *ptr.add(1) }, 20);
assert_eq!((len, cap), (3, 3));

// Hand (ptr, len, cap) to C here.
// Reclaim it later with Vec::from_raw_parts to free the allocation.
let _reclaimed = unsafe { Vec::from_raw_parts(ptr, len, cap) };

It works, but the ManuallyDrop wrapper exists only to keep the destructor from running. Forget it, write mem::forget(v) in the wrong order, or read capacity() after the move and you’ve got a use-after-free or a leak.

The fix: one safe call, three return values

Vec::into_raw_parts(self) -> (*mut T, usize, usize) consumes the Vec, hands you the pointer-length-capacity triple, and leaves the allocation alive for you to manage:

1
2
3
4
5
6
7
8
9
let v: Vec<u32> = vec![10, 20, 30];
let (ptr, len, cap) = v.into_raw_parts();

assert_eq!((len, cap), (3, 3));
assert_eq!(unsafe { *ptr.add(1) }, 20);

// Reclaim and free at the end (or hand to C and have C call back).
let reclaimed = unsafe { Vec::from_raw_parts(ptr, len, cap) };
assert_eq!(reclaimed, vec![10, 20, 30]);

No wrapper, no separate field reads, no chance of accidentally calling a &self method after the move. The method is const, too.

String::into_raw_parts follows the same shape

String gets the same treatment in 1.93. The triple is (*mut u8, usize, usize), which is what String::from_raw_parts wants back:

1
2
3
4
5
6
7
let s = String::from("hello");
let (ptr, len, cap) = s.into_raw_parts();

assert_eq!((len, cap), (5, 5));

let rebuilt = unsafe { String::from_raw_parts(ptr, len, cap) };
assert_eq!(rebuilt, "hello");

The pairing is the point: into_raw_parts is safe (the Vec/String is gone, no aliasing exists yet), and from_raw_parts is unsafe (you’re asserting the triple came from a matching allocator with the right layout). The split keeps the unsafety where it actually lives.

When to reach for it

Any FFI boundary where the C side will hold the buffer for a while: graphics buffers, codec frames, command queues, anything with an extern "C" fn free_my_thing(ptr, len, cap) callback. Also handy when you’re building your own typed handles around a raw allocation — Box::into_raw covers the single-value case; into_raw_parts covers the variable-length one.

If you only need the pointer and nothing will ever reclaim the allocation, Vec::leak is still the shorter call. Reach for into_raw_parts the moment the capacity matters — i.e. anyone, anywhere, might want to give the memory back.

142. Path::absolute — Make a Path Absolute Without Touching the Filesystem

Need an absolute path for a log line, an error message, or a “files will land here” preview — but the file might not exist yet? fs::canonicalize will refuse. std::path::absolute (stable since Rust 1.79) gives you the absolute form without ever opening the disk.

The canonicalize trap

The instinctive choice for “turn this into a full path” is fs::canonicalize. It works — until it doesn’t:

1
2
3
4
use std::fs;

let p = fs::canonicalize("does_not_exist.toml");
assert!(p.is_err()); // canonicalize requires the path to exist

It also resolves symlinks and walks every .. component against the real directory tree. That’s the right behaviour for finding a file. It’s wrong for printing one back to the user before you’ve written it.

path::absolute does the syntactic thing

std::path::absolute joins a relative path with the current working directory and normalises the result. No syscalls beyond looking up the CWD; the file doesn’t have to exist:

1
2
3
4
5
use std::path::absolute;

let p = absolute("config/app.toml").unwrap();
assert!(p.is_absolute());
// e.g. "/work/config/app.toml" — without ever opening anything

If the path is already absolute it’s left alone (modulo platform-specific normalisation). .. components are resolved syntactically, without consulting the filesystem for what each directory really is.

Useful for nicely-formatted output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::path::{absolute, PathBuf};

fn describe(relative: &str) -> String {
    let abs: PathBuf = absolute(relative).unwrap();
    format!("writing to {}", abs.display())
}

let msg = describe("logs/today.log");
assert!(msg.contains("logs/today.log"));
assert!(msg.starts_with("writing to "));

When you’re echoing the user’s choices back to them, or building helpful error messages, this is usually what you want — the path they meant, not whatever the filesystem turned it into.

When to reach for it

Use path::absolute for log lines, config previews, default-location calculations, or any “this is where it will go” message about a file that might not exist yet. Stick with fs::canonicalize when you actually want to follow symlinks and prove the file exists — that’s its job.

Stabilised in Rust 1.79 (June 2024).

141. BinaryHeap::into_sorted_vec — Heapsort in One Call

You stuffed everything into a BinaryHeap to keep “biggest first” cheap, but at the end of the day you want a sorted Vec to hand to the next stage. The pop-loop you almost wrote is built into the type — into_sorted_vec consumes the heap and gives you the ascending-order Vec for free.

The pop-loop

The naive shape: drain the heap with pop and push into a fresh Vec. Pops come out largest-first, so to get ascending order you have to either reverse at the end or push to the front — both add steps for no reason.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::collections::BinaryHeap;

let heap = BinaryHeap::from([3, 1, 4, 1, 5, 9, 2, 6, 5]);

let mut out = Vec::with_capacity(heap.len());
let mut heap = heap;
while let Some(x) = heap.pop() {
    out.push(x);
}
out.reverse(); // pops came out descending — flip them

assert_eq!(out, [1, 1, 2, 3, 4, 5, 5, 6, 9]);

Five lines and a temporary out for what is, in the end, “sort this thing.” The heap is already a heap — you’ve paid for the structure, now you’re throwing it away.

The one-liner

BinaryHeap::into_sorted_vec(self) -> Vec<T> does exactly the drain-and-sort the heap was built for, in O(n log n), and reuses the heap’s allocation as the output Vec. No reverse(), no spare buffer.

1
2
3
4
5
6
use std::collections::BinaryHeap;

let heap = BinaryHeap::from([3, 1, 4, 1, 5, 9, 2, 6, 5]);
let sorted = heap.into_sorted_vec();

assert_eq!(sorted, [1, 1, 2, 3, 4, 5, 5, 6, 9]);

Ascending order, because that’s almost always what the next consumer wants. BinaryHeap is a max-heap, so internally into_sorted_vec repeatedly sifts the max to the end of the backing buffer — the same in-place heapsort you’d write by hand.

Top-k without sorting the whole input

Where this really pays off: “I want the largest k of n items.” Push everything into the heap with Reverse to make it a min-heap-of-size-k, then call into_sorted_vec once at the end:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use std::cmp::Reverse;
use std::collections::BinaryHeap;

fn top_k(items: impl IntoIterator<Item = i32>, k: usize) -> Vec<i32> {
    let mut heap: BinaryHeap<Reverse<i32>> = BinaryHeap::with_capacity(k);
    for x in items {
        if heap.len() < k {
            heap.push(Reverse(x));
        } else if let Some(mut min) = heap.peek_mut() {
            if x > min.0 { *min = Reverse(x); }
        }
    }
    heap.into_sorted_vec()
        .into_iter()
        .map(|Reverse(x)| x)
        .collect()
}

assert_eq!(top_k([7, 3, 9, 1, 8, 2, 6], 3), [9, 8, 7]);

into_sorted_vec returns the Reverse-wrapped items in ascending Reverse order, which is descending by inner value — strip the wrapper with map and the largest of the top-k comes out first, exactly the order a “top” list wants.

When to reach for it

Any time the loop you’re about to write is “pop until empty, collect into Vec.” into_sorted_vec is the same algorithm — heapsort — with one fewer allocation and one fewer reverse. The heap was already half of a sort; let it finish the job.

#140 May 2026

140. slice::as_array — Lock a Slice Into a Fixed-Size Array Reference

A function hands you &[u8] but the next step wants &[u8; 32]. The old answer was <&[u8; 32]>::try_from(slice) — a turbofish-and-trait dance for what is really just a length check. Rust 1.93 stabilises slice::as_array, the method-call version that does exactly that.

The pain: TryFrom for what should be a method

Cryptography APIs, parsers, and FFI all funnel data through &[T], but consumers usually want a concrete &[T; N] so they can index without bounds checks or pattern-match the fields out. The canonical conversion looks like this:

1
2
3
4
5
6
7
8
9
use std::convert::TryFrom;

fn fingerprint(digest: &[u8]) -> Result<&[u8; 32], &'static str> {
    <&[u8; 32]>::try_from(digest).map_err(|_| "expected 32 bytes")
}

let bytes = [0u8; 32];
assert!(fingerprint(&bytes[..]).is_ok());
assert!(fingerprint(&bytes[..16]).is_err());

It works, but every time you have to remember to write <&[u8; 32]>::try_from(...) with the reference inside the turbofish — write <[u8; 32]>::try_from instead and you’ll get an owned array (and a T: Clone bound) that you didn’t ask for. The error message when a coworker gets the form wrong is its own little adventure.

The fix: a method on [T] that returns Option<&[T; N]>

<[T]>::as_array::<N> is a plain method call. The length check is the same — the whole slice must be exactly N long — but the call site reads like every other slice method:

1
2
3
4
5
6
7
let bytes: &[u8] = &[0u8; 32];

let arr: Option<&[u8; 32]> = bytes.as_array();
assert!(arr.is_some());

let short: &[u8] = &bytes[..16];
assert!(short.as_array::<32>().is_none());

No TryFrom import, no angle-brackets-around-a-reference. The turbofish goes on the method, where it belongs, and the return type is Option — which is what you wanted anyway when the conversion can fail.

as_mut_array for the writeable side

The same trick works through &mut [T]. Useful when you’ve been handed a sub-slice of a buffer and want to write a fixed-size record into it without indexing each field:

1
2
3
4
5
let mut buf = [0u8; 16];
let header: &mut [u8; 4] = (&mut buf[..4]).as_mut_array().unwrap();
*header = *b"RIFF";

assert_eq!(&buf[..4], b"RIFF");

as_array and as_mut_array mirror <&[T; N]>::try_from / <&mut [T; N]>::try_from exactly — same semantics, fewer keystrokes, better error type.

Pair it with split_first_chunk for parsers

If you only need the first N bytes (and want to keep the rest), reach for split_first_chunk::<N> from the existing family. Use as_array when the slice is supposed to be exactly the right length — return values from a digest() call, decoded base64 of known size, FFI buffers carved to spec:

1
2
3
4
5
6
fn parse_ipv4(bytes: &[u8]) -> Option<[u8; 4]> {
    bytes.as_array::<4>().copied()
}

assert_eq!(parse_ipv4(&[192, 168, 1, 1]), Some([192, 168, 1, 1]));
assert_eq!(parse_ipv4(&[10, 0, 0]), None);

.copied() turns &[u8; 4] into [u8; 4] when you want an owned array — works because [u8; 4]: Copy.

When to reach for it

Anywhere you currently write <&[T; N]>::try_from(slice).ok() or slice.try_into().ok() and have to annotate the type to steer the inference. as_array is shorter, the failure case is an Option instead of a Result<_, TryFromSliceError>, and the turbofish sits where every other generic slice method puts it. Small ergonomics, but you’ll write it ten times this week.

#139 May 2026

139. HashMap::extract_if — Drain Matching Entries Without Losing Them

HashMap::retain discards what it removes. When you actually want those values — to log them, ship them, or move them elsewhere — you used to clone the keys and double-walk the map. extract_if, stable since Rust 1.88, hands them back as an iterator.

retain throws the babies out with the bathwater

retain is the natural “filter a map in place” call, but its return type is (). The entries it kicks out vanish:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::collections::HashMap;

let mut sessions: HashMap<&str, u32> = HashMap::from([
    ("alice", 12),
    ("bob",    0),
    ("carol",  3),
    ("dan",    0),
]);

sessions.retain(|_user, hits| *hits > 0);

assert_eq!(sessions.len(), 2);
// Who got dropped? Gone. We can't tell anyone.

If you wanted to log the expired sessions, notify their owners, or forward them to another map, retain isn’t enough. The textbook workaround is collect-then-remove:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::collections::HashMap;

let mut sessions: HashMap<String, u32> = HashMap::from([
    ("alice".into(), 12),
    ("bob".into(),    0),
    ("carol".into(),  3),
    ("dan".into(),    0),
]);

let to_evict: Vec<String> = sessions
    .iter()
    .filter(|(_, hits)| **hits == 0)
    .map(|(k, _)| k.clone())          // forced clone to break the self-borrow
    .collect();

let mut evicted = Vec::new();
for k in to_evict {
    if let Some(v) = sessions.remove(&k) {
        evicted.push((k, v));
    }
}

assert_eq!(evicted.len(), 2);

Two passes, a throwaway Vec<K>, and a hard K: Clone requirement just so the borrow checker stops yelling.

extract_if is one pass and gives the values back

HashMap::extract_if(pred) returns an iterator that yields (K, V) for every entry whose predicate fires true, removing them from the map as it goes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::collections::HashMap;

let mut sessions: HashMap<&str, u32> = HashMap::from([
    ("alice", 12),
    ("bob",    0),
    ("carol",  3),
    ("dan",    0),
]);

let evicted: HashMap<&str, u32> =
    sessions.extract_if(|_user, hits| *hits == 0).collect();

assert_eq!(evicted.len(), 2);
assert_eq!(sessions.len(), 2);
assert!(sessions.contains_key("alice"));
assert!(sessions.contains_key("carol"));

No clone, no temporary Vec, no second walk. The removed entries land in whatever collection you choose — another HashMap, a Vec<(K, V)>, a channel, a log line. HashMap order isn’t guaranteed, but neither was it before.

The predicate gets &mut V, so it can edit survivors

The closure signature is FnMut(&K, &mut V) -> bool. Return true to extract, false to keep — and because you’ve got &mut V in hand either way, you can update entries you choose to leave behind:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::collections::HashMap;

let mut scores: HashMap<&str, i32> = HashMap::from([
    ("alice", 85),
    ("bob",   42),
    ("carol", 91),
    ("dan",   30),
]);

let failing: HashMap<&str, i32> = scores
    .extract_if(|_name, score| {
        if *score < 50 {
            true             // pull this one out
        } else {
            *score += 5;     // bump the survivors
            false            // keep them
        }
    })
    .collect();

assert_eq!(failing.len(), 2);
assert_eq!(scores[&"alice"], 90);
assert_eq!(scores[&"carol"], 96);

One traversal, three jobs: filter, remove-and-collect, and patch in place.

Drop the iterator early and the rest stay

The iterator only removes entries it has visited. If you stop iterating — break, take(n), drop the iterator — anything not yet inspected stays in the map untouched:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::collections::HashMap;

let mut nums: HashMap<i32, i32> = (0..10).map(|i| (i, i)).collect();

// Pull at most two even entries out, then stop.
let pulled: Vec<(i32, i32)> = nums
    .extract_if(|_, v| *v % 2 == 0)
    .take(2)
    .collect();

assert_eq!(pulled.len(), 2);
// 10 - 2 = 8 left, regardless of which evens were taken.
assert_eq!(nums.len(), 8);

When to reach for it

Whenever you’d otherwise write the clone-keys-and-double-pass shuffle: expiring sessions, draining tasks that hit a deadline, partitioning a cache by tenant, moving “done” items into an archive map. HashSet::extract_if is the same idea for sets — predicate is FnMut(&T) -> bool. The mental rule: reach for retain when the discarded entries are truly garbage, and extract_if when they still have a job to do.

#138 May 2026

138. iter::zip — Parallel Iteration Without the Method-Chain Dance

a.iter().zip(b.iter()) makes one collection the star and the other an awkward sidekick. std::iter::zip puts them on equal footing — two arguments, same indent, no method-chain twist.

The Problem

You have two collections that line up element-for-element. Pair them and walk through together:

1
2
3
4
5
6
let names = ["Alice", "Bob", "Carol"];
let ages  = [30, 25, 40];

for (name, age) in names.iter().zip(ages.iter()) {
    println!("{name}: {age}");
}

Functionally fine — but names is the subject of the chain while ages is shoved into an argument slot. When both collections are equally important to the loop, the asymmetry hides intent. Worse, swap one side for an owned value and the call site gets uglier: names.iter().zip(ages.into_iter()).

The Free Function

std::iter::zip is the same combinator, only as a free function. Both iterables sit at the same level:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::iter::zip;

let names = ["Alice", "Bob", "Carol"];
let ages  = [30, 25, 40];

for (name, age) in zip(&names, &ages) {
    println!("{name}: {age}");
}

let pairs: Vec<(&&str, &i32)> = zip(&names, &ages).collect();
assert_eq!(pairs.len(), 3);
assert_eq!(*pairs[0].0, "Alice");
assert_eq!(*pairs[0].1, 30);

Both arguments are IntoIterator, so you can pass owned values, slices, or references directly — no per-side .iter() decoration to keep balanced.

Mixing Owned and Borrowed

The asymmetry of the method form shows up most when one side is owned:

1
2
3
4
5
6
7
8
use std::iter::zip;

let labels = vec!["x", "y", "z"];
let values: [i32; 3] = [10, 20, 30];

// No .iter() / .into_iter() bookkeeping — zip handles both.
let total: i32 = zip(labels, values).map(|(_, v)| v).sum();
assert_eq!(total, 60);

Same IntoIterator rules as a for loop: arrays by value yield items by value, references yield references.

Same Semantics as Iterator::zip

iter::zip(a, b) is exactly a.into_iter().zip(b). It stops at the shorter side and yields (A, B) tuples — no surprises:

1
2
3
4
5
6
7
use std::iter::zip;

let short = [1, 2];
let long  = [10, 20, 30, 40];

let pairs: Vec<(i32, i32)> = zip(short, long).collect();
assert_eq!(pairs, [(1, 10), (2, 20)]);

When to Reach for It

Use iter::zip when both sides are equal citizens of the loop — typical of parallel data, column-by-column processing, or test setup where inputs and expected outputs travel together. Stick with the method form when you’re already deep in an iterator chain and zipping is just one more step. Stable since Rust 1.59.

#137 May 2026

137. Vec::retain_mut — Filter and Edit In Place, In One Pass

retain decides who stays, but its closure only sees &T — so when you also want to tweak the survivors, you end up making two passes. retain_mut collapses that into one.

The Problem

Imagine a Vec<Job> where each surviving job needs its retry counter bumped on the way through. With plain retain, the closure gets a shared reference, so the mutation has to happen separately:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct Job { id: u32, retries: u32, done: bool }

let mut jobs = vec![
    Job { id: 1, retries: 0, done: false },
    Job { id: 2, retries: 0, done: true  },
    Job { id: 3, retries: 0, done: false },
];

// Pass 1: bump the survivors.
for j in jobs.iter_mut().filter(|j| !j.done) {
    j.retries += 1;
}
// Pass 2: drop the finished ones.
jobs.retain(|j| !j.done);

Two passes, two intent-leaks, and the predicate is duplicated. Refactor one and forget to refactor the other — welcome to a subtle bug.

retain_mut: One Pass, Mutable Access

Vec::retain_mut gives the closure a &mut T. Edit the element and return whether to keep it — same call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
struct Job { id: u32, retries: u32, done: bool }

let mut jobs = vec![
    Job { id: 1, retries: 0, done: false },
    Job { id: 2, retries: 0, done: true  },
    Job { id: 3, retries: 0, done: false },
];

jobs.retain_mut(|j| {
    if j.done { return false; }
    j.retries += 1;
    true
});

assert_eq!(jobs.len(), 2);
assert_eq!(jobs[0].id, 1);
assert_eq!(jobs[0].retries, 1);
assert_eq!(jobs[1].id, 3);
assert_eq!(jobs[1].retries, 1);

One scan, no duplicated predicate, and the order of the kept elements is preserved.

The Sneaky Trick: Mutate-Then-Check

The mutation happens before the closure returns, so a retain_mut can also normalize values and then filter on the normalized form:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let mut words = vec![
    String::from("  hello "),
    String::from("   "),
    String::from(" Rust"),
];

words.retain_mut(|s| {
    *s = s.trim().to_string();
    !s.is_empty()
});

assert_eq!(words, vec!["hello", "Rust"]);

Trim everything, then drop the blanks — without ever allocating a second Vec.

When to Reach for It

Use retain_mut whenever the keep/drop decision and an in-place edit travel together. Same goes for VecDeque::retain_mut and LinkedList::retain_mut — same shape, same payoff. If the closure is purely read-only, stick with retain and keep the intent narrow.

#136 May 2026

136. LazyLock::force_mut — Mutate a Lazy Value Without Wrapping It in a Mutex

Got a LazyLock you own outright? With force_mut (stable in Rust 1.94), you can initialize and mutate it through &mut LazyLock — no Mutex, no RwLock, no locking dance.

The Problem

LazyLock is perfect for one-time initialization, but its Deref only hands out a shared reference. If you want to mutate the inner value, the textbook move is to wrap it in Mutex<T>:

1
2
3
4
5
6
7
use std::sync::{LazyLock, Mutex};

static CACHE: LazyLock<Mutex<Vec<String>>> = LazyLock::new(|| Mutex::new(Vec::new()));

fn add(s: String) {
    CACHE.lock().unwrap().push(s);
}

That’s the right answer for shared global state. But when you actually have exclusive ownership — a struct field, a builder, a test fixture — the Mutex is pure ceremony.

force_mut: Init and Mutate in One Step

If you have &mut LazyLock<T>, you already have exclusive access. Synchronization is moot. force_mut exploits that: it triggers initialization if needed, then hands you a plain &mut T.

1
2
3
4
5
6
7
8
9
use std::sync::LazyLock;

let mut tags = LazyLock::new(|| vec!["rust"]);

let v: &mut Vec<&'static str> = LazyLock::force_mut(&mut tags);
v.push("std");
v.push("lazy");

assert_eq!(*v, vec!["rust", "std", "lazy"]);

No Mutex, no .lock().unwrap(), no poisoning to handle. The init closure runs at most once, and from then on you can mutate freely through &mut.

get_mut: Mutate Only If Already Initialized

The sibling, LazyLock::get_mut, returns Option<&mut T> and won’t trigger init:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::sync::LazyLock;

let mut counts: LazyLock<Vec<u32>> = LazyLock::new(|| vec![0u32; 8]);

// Hasn't been touched yet — get_mut returns None.
assert!(LazyLock::get_mut(&mut counts).is_none());

// Force initialization explicitly:
LazyLock::force_mut(&mut counts)[0] = 42;

// Now it's available without re-running the closure:
let slot = LazyLock::get_mut(&mut counts).unwrap();
slot[1] = 99;

assert_eq!(*slot, [42, 99, 0, 0, 0, 0, 0, 0]);

Useful when you’d rather skip work entirely if it never happened — “flush the cache on shutdown, but only if anyone built it.”

When to Reach for It

Pick force_mut whenever you own the LazyLock outright and would otherwise wrap it in Mutex<T> just to get mutation. It’s perfect for struct fields, test fixtures, builders, and anything else where you already have &mut to the container.

LazyCell::force_mut and LazyCell::get_mut ship the same shape for the single-thread cell — pick whichever matches your sync story.

#135 May 2026

135. str::strip_prefix — Trim a Prefix Without Slicing by Hand

Reaching for if s.starts_with("foo") { &s[3..] } to drop a prefix? That’s an off-by-one waiting to happen — and a panic the first time someone passes in an emoji. str::strip_prefix returns Option<&str> and gets it right by construction.

The Problem

You want the part of a string after a known prefix:

1
2
3
4
5
6
7
8
let s = "Bearer abc123";

let token = if s.starts_with("Bearer ") {
    &s[7..]
} else {
    s
};
assert_eq!(token, "abc123");

Two things wrong here: the literal 7 has to stay in sync with the literal "Bearer ", and slicing by byte offset will panic if the prefix ever lands mid-codepoint. Even using prefix.len() only saves you from the first bug, not the second when the prefix is dynamic.

The Fix: strip_prefix

1
2
3
4
let s = "Bearer abc123";

let token = s.strip_prefix("Bearer ").unwrap_or(s);
assert_eq!(token, "abc123");

strip_prefix returns Some(&str) if the prefix matched (giving you the rest), or None if it didn’t. No magic numbers, no slicing, no UTF-8 footguns — the prefix length comes from the prefix itself.

Pattern Matching, Not Just Strings

The argument is anything implementing Pattern, so a char, a closure, or even an array of chars all work:

1
2
3
4
5
6
assert_eq!("-x".strip_prefix('-'), Some("x"));
assert_eq!("x".strip_prefix('-'), None);

// Trim any leading whitespace character
let s = "\t  hello".strip_prefix(|c: char| c.is_whitespace());
assert_eq!(s, Some("  hello"));

Note this only strips one match — the char form doesn’t loop. For “strip every leading space,” reach for trim_start_matches.

The Twin: strip_suffix

Same shape, other end:

1
2
3
4
let filename = "report.tar.gz";

let stem = filename.strip_suffix(".gz").unwrap_or(filename);
assert_eq!(stem, "report.tar");

Together they replace half the manual &s[..s.len() - 3] arithmetic you’d otherwise write — and the Option return makes “did it actually have the prefix?” a value, not a separate starts_with call.

#134 May 2026

134. Iterator::find_map — Find and Transform in One Pass

Looking for the first element that matches and needs to come back as something else? Skip the filter_map(...).next() two-step — find_map says it in one call.

The Problem

You have an iterator and want the first item that satisfies a condition plus the value derived from it. The hand-rolled version is a for loop with a mutable binding and a break:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let inputs = ["abc", "12", "def", "34"];

let mut first: Option<i32> = None;
for s in &inputs {
    if let Ok(n) = s.parse::<i32>() {
        first = Some(n);
        break;
    }
}
assert_eq!(first, Some(12));

You can compress it with filter_map(...).next():

1
2
3
4
5
let first: Option<i32> = inputs
    .iter()
    .filter_map(|s| s.parse::<i32>().ok())
    .next();
assert_eq!(first, Some(12));

It’s shorter, but what you actually mean — find the first one — is buried inside “filter everything, then take one.”

The Fix: find_map

Iterator::find_map takes a closure returning Option<U> and returns the first Some(U) it produces — short-circuiting as soon as the closure says yes:

1
2
3
4
let first: Option<i32> = inputs
    .iter()
    .find_map(|s| s.parse::<i32>().ok());
assert_eq!(first, Some(12));

Same short-circuit behavior as find, but the closure does the transformation too — no separate map step on the result.

Where It Earns Its Keep

Looking up the first input that’s in a small table:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn level(name: &str) -> Option<u8> {
    match name {
        "trace" => Some(0),
        "debug" => Some(1),
        "info"  => Some(2),
        _ => None,
    }
}

let inputs = ["??", "huh", "info", "debug"];
let first = inputs.iter().find_map(|s| level(s));
assert_eq!(first, Some(2));

Pulling the first error out of a batch of Results without losing the message:

1
2
3
4
let results: Vec<Result<i32, &str>> = vec![Ok(1), Ok(2), Err("bad row"), Ok(3)];

let first_err = results.iter().find_map(|r| r.as_ref().err().copied());
assert_eq!(first_err, Some("bad row"));

Anywhere you catch yourself writing .filter_map(...).next() or a manual loop with break, find_map says the same thing with less noise.

#133 May 2026

133. slice::rotate_left — Cycle Elements Through a Slice Without a Second Buffer

Need the first few elements to wrap around to the back? slice.rotate_left(n) cycles them through in place — no scratch Vec, no clever indexing, no borrow checker drama.

The Problem

Rotating a slice “the obvious way” means allocating a temporary, copying things twice, and being very careful about ranges:

1
2
3
4
5
6
7
8
9
fn rotate_left_manual(v: &mut Vec<i32>, n: usize) {
    let mut tmp: Vec<i32> = v[..n].to_vec();
    v.drain(..n);
    v.append(&mut tmp);
}

let mut data = vec![1, 2, 3, 4, 5];
rotate_left_manual(&mut data, 2);
assert_eq!(data, vec![3, 4, 5, 1, 2]);

It works, but it allocates and only runs on Vec. The moment you only have a &mut [T] — a window inside a larger buffer, say — to_vec/drain aren’t options at all.

After: rotate_left and rotate_right

Every slice already knows how to rotate itself in place:

1
2
3
4
5
6
7
let mut data = [1, 2, 3, 4, 5];
data.rotate_left(2);
assert_eq!(data, [3, 4, 5, 1, 2]);

let mut data = [1, 2, 3, 4, 5];
data.rotate_right(2);
assert_eq!(data, [4, 5, 1, 2, 3]);

rotate_left(n) moves the first n elements to the end; rotate_right(n) moves the last n to the front. Both run in O(len) with zero allocations, and they work on any &mut [T] — arrays, vector slices, sub-ranges of bigger buffers.

Where It Earns Its Keep

Round-robin scheduling: the first runner takes a turn, then moves to the back of the line.

1
2
3
4
5
6
7
8
let mut queue = ["alice", "bob", "carol", "dave"];

for _ in 0..queue.len() {
    println!("now serving: {}", queue[0]);
    queue.rotate_left(1);
}
// queue is back to its original order
assert_eq!(queue, ["alice", "bob", "carol", "dave"]);

Scrolling a fixed-size display buffer — drop the oldest row, leave a slot at the end for the newest:

1
2
3
4
let mut rows = [10, 20, 30, 40];
rows.rotate_left(1);
rows[rows.len() - 1] = 99;
assert_eq!(rows, [20, 30, 40, 99]);

And because it operates on &mut [T], you can rotate a window inside a larger buffer without splitting it:

1
2
3
let mut buf = [0, 1, 2, 3, 4, 5, 6, 7];
buf[2..6].rotate_left(1);
assert_eq!(buf, [0, 1, 3, 4, 5, 2, 6, 7]);

Anywhere you’d reach for “shift everything left and stick the front on the end,” rotate_left does it in one line with no allocation.

#131 May 2026

131. mem::offset_of! — Byte Offsets Without the memoffset Crate

You need the byte offset of a field — for FFI, custom serialization, or talking to a C struct. The old answer was unsafe pointer arithmetic on a MaybeUninit, or pulling in the memoffset crate. std::mem::offset_of! is the safe, one-liner replacement.

The problem

Say you’re matching a C layout and need to know exactly where each field lives in memory:

1
2
3
4
5
6
7
#[repr(C)]
struct Header {
    magic: u32,
    version: u16,
    flags: u16,
    payload_len: u64,
}

The pre-1.77 way meant either an external crate or hand-rolled unsafe:

1
2
3
4
5
6
7
8
use std::mem::MaybeUninit;

fn payload_len_offset_old() -> usize {
    let uninit = MaybeUninit::<Header>::uninit();
    let base = uninit.as_ptr() as usize;
    let field = unsafe { &raw const (*uninit.as_ptr()).payload_len } as usize;
    field - base
}

It works, but unsafe, raw pointers, and a MaybeUninit is a lot of ceremony for “where does this field start?”

The fix: mem::offset_of!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::mem::offset_of;

let magic_off       = offset_of!(Header, magic);
let version_off     = offset_of!(Header, version);
let flags_off       = offset_of!(Header, flags);
let payload_len_off = offset_of!(Header, payload_len);

assert_eq!(magic_off, 0);
assert_eq!(version_off, 4);
assert_eq!(flags_off, 6);
assert_eq!(payload_len_off, 8);

No unsafe. No allocation. No instance of Header ever exists. The macro expands to a const-evaluable usize — usable inside const fn and static items.

Nested fields work too

Dot through a path of named fields and offset_of! keeps walking:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#[repr(C)]
struct Inner {
    a: u32,
    b: u32,
}

#[repr(C)]
struct Outer {
    tag: u8,
    _pad: [u8; 3],
    inner: Inner,
}

assert_eq!(offset_of!(Outer, inner), 4);
assert_eq!(offset_of!(Outer, inner.b), 8);

Tuples and tuple structs use numeric indices:

1
2
3
4
5
#[repr(C)]
struct Pair(u8, u32);

assert_eq!(offset_of!(Pair, 0), 0);
assert_eq!(offset_of!(Pair, 1), 4);

When it earns its keep

FFI bindings, custom binary parsers, kernel-style intrusive data structures, and anywhere you’d otherwise reach for memoffset. The macro is in core, so it works in no_std. Reach for it whenever you find yourself writing as *const _ as usize math.

#130 May 2026

130. mem::discriminant — Compare Enum Variants, Ignore the Payload

You want to know “is this another Click?” — not whether the coordinates match. Hand-rolling a match for every variant gets old fast. std::mem::discriminant answers that question in one call.

The problem

Say you have an event enum with payload data on every variant:

1
2
3
4
5
6
#[derive(Debug)]
enum Event {
    Click { x: i32, y: i32 },
    KeyPress(char),
    Scroll(i32),
}

Two Clicks with different coordinates are still both clicks. Deriving PartialEq won’t help — that compares the inner data too. The usual workaround is a tedious match:

1
2
3
4
5
6
7
8
fn same_kind_match(a: &Event, b: &Event) -> bool {
    match (a, b) {
        (Event::Click { .. }, Event::Click { .. }) => true,
        (Event::KeyPress(_), Event::KeyPress(_)) => true,
        (Event::Scroll(_), Event::Scroll(_)) => true,
        _ => false,
    }
}

Every new variant means another arm. Forget one and you’ve got a silent bug.

The fix: mem::discriminant

1
2
3
4
5
use std::mem::discriminant;

fn same_kind(a: &Event, b: &Event) -> bool {
    discriminant(a) == discriminant(b)
}

discriminant(&value) returns an opaque Discriminant<T> representing which variant the value is — nothing more. Two Clicks with wildly different coordinates have the same discriminant; a Click and a KeyPress don’t.

No match, no missing-arm bugs, no recompile when you add a new variant.

Bonus: Discriminant<T> is Hash + Eq + Copy

That makes it a perfectly good HashMap key, which is great for counting events by variant without writing a tag enum:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::collections::HashMap;
use std::mem::discriminant;

let events = [
    Event::Click { x: 0, y: 0 },
    Event::KeyPress('a'),
    Event::Click { x: 1, y: 1 },
    Event::Scroll(5),
    Event::KeyPress('b'),
];

let mut counts: HashMap<_, usize> = HashMap::new();
for ev in &events {
    *counts.entry(discriminant(ev)).or_insert(0) += 1;
}
// counts now holds: Click -> 2, KeyPress -> 2, Scroll -> 1

Reach for discriminant whenever you find yourself writing a kind() method or an “is this the same variant?” match. The std lib already has it.

#129 May 2026

129. BTreeMap::extract_if — Range-Scoped Filter-and-Remove in One Pass

Vec::extract_if was great. The BTreeMap version, stable since Rust 1.91, adds a trick the slice variant can’t pull off — a range bound that scopes the scan to part of the keyspace.

The collect-keys-then-remove dance

You want to remove every entry matching a predicate from a BTreeMap, and keep the removed entries. The borrow checker won’t let you mutate the map while you’re iterating it, so the textbook workaround is a two-pass clone-the-keys shuffle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::collections::BTreeMap;

let mut events: BTreeMap<u64, String> = BTreeMap::from([
    (1, "boot".into()),
    (2, "login".into()),
    (3, "click".into()),
    (4, "logout".into()),
]);

let cutoff = 3;

let to_remove: Vec<u64> = events
    .iter()
    .filter(|(k, _)| **k < cutoff)
    .map(|(k, _)| *k)            // requires K: Copy or Clone
    .collect();

let mut expired = Vec::new();
for k in to_remove {
    if let Some(v) = events.remove(&k) {
        expired.push((k, v));
    }
}

assert_eq!(expired.len(), 2);

It works, but the costs add up. You walk the map twice, you require K: Clone (or Copy), and you allocate a throwaway Vec<K> just to break the self-borrow.

extract_if is one pass — and it takes a range

BTreeMap::extract_if(range, pred) returns an iterator that visits keys in ascending order inside the given range, and yields (K, V) for every entry whose predicate returns true. The map’s structure is updated as the iterator advances:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::collections::BTreeMap;

let mut events: BTreeMap<u64, &str> = BTreeMap::from([
    (1, "boot"),
    (2, "login"),
    (3, "click"),
    (4, "logout"),
    (5, "shutdown"),
]);

let expired: BTreeMap<u64, &str> =
    events.extract_if(..3, |_k, _v| true).collect();

assert_eq!(expired.into_iter().collect::<Vec<_>>(), [(1, "boot"), (2, "login")]);
assert_eq!(events.keys().copied().collect::<Vec<_>>(), [3, 4, 5]);

No Clone bound. No second pass. No temp Vec<K>. The range ..3 does double duty as a prune — keys outside it aren’t even visited, which matters when the map is large and the range is small.

The closure can mutate survivors too

The predicate signature is FnMut(&K, &mut V) -> bool. Returning true removes and yields; returning false keeps the entry in the map — but you’ve still got &mut V, so you can edit it on the way past:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::collections::BTreeMap;

let mut scores: BTreeMap<&str, i32> = BTreeMap::from([
    ("alice", 85),
    ("bob",   42),
    ("carol", 91),
    ("dan",   30),
]);

let failing: Vec<(&str, i32)> = scores
    .extract_if(.., |_k, v| {
        if *v < 50 {
            true            // pull this one out
        } else {
            *v += 5;        // bump the survivors by 5
            false           // keep it
        }
    })
    .collect();

assert_eq!(failing, [("bob", 42), ("dan", 30)]);
assert_eq!(scores[&"alice"], 90);
assert_eq!(scores[&"carol"], 96);

One traversal, three behaviors at once: filter, remove, and update.

Constraints

The range is RangeBounds<K>, so anything from .. to start..=end works. extract_if panics if start > end or if the bounds collapse to an empty exclusive range. BTreeSet::extract_if is the same idea minus the value — predicate signature is FnMut(&T) -> bool.

When to reach for it

Whenever the operation is “walk a sorted map, peel off the entries matching a condition, optionally bounded to a key range”. Expiring old timestamped events. Draining tasks below a priority watermark. Splitting a map at a tenant boundary into “keep” and “ship”. The range bound is the part the Vec::extract_if (bite 43) can’t give you — and on a large map, scoping the scan is the whole point.

#128 May 2026

128. slice::copy_within — Shift Bytes In-Place Without Fighting the Borrow Checker

You want to copy buf[2..5] over buf[0..3] — same buffer, no allocation. Reach for copy_from_slice and the borrow checker says no. copy_within is the one-call answer.

The two-borrow trap

The obvious code can’t compile — buf would be borrowed mutably and immutably at the same time:

1
2
3
4
5
let mut buf = [1, 2, 3, 4, 5];

// error[E0502]: cannot borrow `buf` as immutable
//               while also borrowed as mutable
// buf[0..3].copy_from_slice(&buf[2..5]);

The usual workarounds are noisy. split_at_mut to carve the slice into two non-overlapping halves:

1
2
3
4
let mut buf = [1, 2, 3, 4, 5];
let (left, right) = buf.split_at_mut(2);
left[0..2].copy_from_slice(&right[0..2]);
assert_eq!(buf, [3, 4, 3, 4, 5]);

…which only works when source and destination land on opposite sides of the split. Otherwise you allocate a throwaway Vec just to break the borrow:

1
2
3
let mut buf = [1, 2, 3, 4, 5];
let tmp: Vec<_> = buf[2..5].to_vec();   // allocation just to copy
buf[0..3].copy_from_slice(&tmp);

copy_within is the primitive

<[T]>::copy_within(src, dest) copies a range of elements to a destination index inside the same slice — one call, no allocation, no split:

1
2
3
let mut buf = [1, 2, 3, 4, 5];
buf.copy_within(2..5, 0);
assert_eq!(buf, [3, 4, 5, 4, 5]);

It’s memmove semantics, so overlapping source and destination just work — the elements that get overwritten don’t matter, the surviving order does:

1
2
3
4
let mut buf = [1, 2, 3, 4, 5];
// shift everything one slot to the right
buf.copy_within(0..4, 1);
assert_eq!(buf, [1, 1, 2, 3, 4]);

Try writing that with split_at_mut — you can’t, the source and destination overlap.

A real shape: drop the first N from a Vec

Removing the first n elements without reallocating is copy_within plus a truncate:

1
2
3
4
5
6
7
8
let mut data: Vec<u8> = vec![10, 20, 30, 40, 50];
let n = 2;
let len = data.len();

data.copy_within(n..len, 0);
data.truncate(len - n);

assert_eq!(data, [30, 40, 50]);

Same allocation, same backing buffer — the values just shift down. Vec::drain(..n) reads cleaner for one-offs, but copy_within is what you want when you’re already holding &mut [T] and can’t reach for Vec methods (think ring buffers, fixed-size scratch arrays, no_std crates).

Constraints

T: Copy is required — the method does a memmove, it doesn’t run destructors or call Clone. Source and destination ranges must both fit inside the slice; otherwise it panics. The destination is a single index (where the copy starts), not a range — the length is taken from the source range.

When to reach for it

Any time you’d otherwise write split_at_mut just to satisfy the borrow checker, or allocate a temporary buffer to break a self-borrow. copy_within reads as what you actually meant: move these bytes over there, in place.

Stable since Rust 1.37. Works on [T], Vec<T>, and any DerefMut<Target = [T]>.

#127 May 2026

127. std::mem::swap — Trade Two Values Through Their &mut

The textbook let tmp = a; a = b; b = tmp; falls over the moment a and b are &mut T — you can’t move out of a reference. mem::swap is the generic, safe, two-line answer.

Why the temp-variable dance breaks

If a and b are owned locals, you can shuffle through a temporary just fine. As soon as they’re behind &mut, the borrow checker stops you:

1
2
3
4
5
fn naive<T>(a: &mut T, b: &mut T) {
    // let tmp = *a;   // E0507: cannot move out of `*a`
    // *a = *b;        // (also moves out of *b)
    // *b = tmp;
}

You’d have to require T: Copy (loses generality), T: Clone (extra work), or reach for unsafe { ptr::swap(...) }. None of those is the right answer.

mem::swap is the primitive

std::mem::swap(&mut a, &mut b) swaps the bits behind two mutable references. No traits required, no allocation, no unsafe:

1
2
3
4
5
6
7
8
9
use std::mem;

let mut a = String::from("left");
let mut b = String::from("right");

mem::swap(&mut a, &mut b);

assert_eq!(a, "right");
assert_eq!(b, "left");

Works for any T. The two memory locations exchange contents in-place — one memcpy-sized swap, no clones.

A real shape: front/back double buffering

The pattern that earns mem::swap its keep — flip a “current” and “next” buffer at the end of each frame, reuse both allocations:

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

struct DoubleBuffer<T> {
    front: Vec<T>,
    back: Vec<T>,
}

impl<T> DoubleBuffer<T> {
    fn flip(&mut self) {
        mem::swap(&mut self.front, &mut self.back);
    }
}

let mut buf = DoubleBuffer {
    front: vec![1, 2, 3],
    back:  vec![9, 9, 9],
};

buf.flip();

assert_eq!(buf.front, vec![9, 9, 9]);
assert_eq!(buf.back,  vec![1, 2, 3]);

Two fields of the same struct, both behind &mut self — exactly the case the temp-variable dance can’t reach. mem::swap doesn’t care that the references come from the same parent borrow.

Why not slice::swap or Vec::swap?

v.swap(i, j) is the right tool when both values live in the same slice — it does the index trick under the hood so the borrow checker stays happy. mem::swap is the broader primitive: any two &mut T, regardless of whether they share a container. They’re the same idea at different scopes.

When to reach for it

Whenever you need to exchange two owned values through &mut: flipping buffers, rotating state in a tree node, splicing nodes in a linked list, swapping fields during a state transition. mem::take is swap with T::default() on one side; mem::replace is swap with src on one side and the old value returned. Same family — pick the one whose shape matches what you actually want back.

#126 May 2026

126. Vec::split_off — Cut a Vec in Two and Keep Both Halves

You want the tail of a Vec as its own owned collection — the head stays put, the tail walks away. Cloning a slice works for Clone types, but breaks the moment your elements aren’t cloneable. Vec::split_off doesn’t care.

The clone-and-truncate dance

The textbook split: copy the tail with to_vec(), then truncate the original.

1
2
3
4
5
6
7
let mut all = vec![1, 2, 3, 4, 5, 6];

let tail: Vec<i32> = all[3..].to_vec();
all.truncate(3);

assert_eq!(all,  vec![1, 2, 3]);
assert_eq!(tail, vec![4, 5, 6]);

It works, but it clones every element of the tail. Fine for i32, wasteful for String, and a hard error for any type that isn’t Clone.

split_off moves, doesn’t clone

Vec::split_off(at) consumes the elements at at.. out of the original Vec and returns them as a new Vec. The elements are moved, not copied — so it works for any T, Clone or not:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let mut tasks: Vec<Box<dyn FnOnce()>> = vec![
    Box::new(|| println!("a")),
    Box::new(|| println!("b")),
    Box::new(|| println!("c")),
    Box::new(|| println!("d")),
];

// Box<dyn FnOnce()> isn't Clone — `tasks[2..].to_vec()` won't compile.
let later = tasks.split_off(2);

assert_eq!(tasks.len(), 2);
assert_eq!(later.len(), 2);

The original keeps [0..at), the returned Vec gets [at..len), and not a single element is duplicated. at == 0 gives you the whole thing in the new Vec (the original ends up empty); at == len gives you an empty new Vec. Anything past the length panics.

A real shape: page-by-page draining

split_off shines when you want to peel a batch off the front of a queue and hand it to a worker, keeping the rest for next time:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fn next_batch<T>(queue: &mut Vec<T>, size: usize) -> Vec<T> {
    let take = size.min(queue.len());
    let rest = queue.split_off(take);
    // queue now holds the first `take` items — that's our batch.
    // `rest` holds everything else — put it back as the new queue.
    let batch = std::mem::replace(queue, rest);
    batch
}

let mut q = vec!["a", "b", "c", "d", "e"];
let first  = next_batch(&mut q, 2);
let second = next_batch(&mut q, 2);

assert_eq!(first,  vec!["a", "b"]);
assert_eq!(second, vec!["c", "d"]);
assert_eq!(q,      vec!["e"]);

No clone, no temporary Vec, no fighting the borrow checker over slice ranges. Two memcpys and you’re done.

When to reach for it

Use split_off whenever you need both halves of a Vec as owned collections — batching, chunked processing, splitting state between threads. If you only want to iterate the tail and throw it away, drain(at..) is better; if you want to keep it, split_off is the move.

#125 May 2026

125. RwLockWriteGuard::downgrade — Hand a Write Lock Off as a Read, Atomically

You took a write lock, updated the data, and now you only want to read. Dropping the write guard and re-acquiring as a reader leaves a window where another writer can slip in. downgrade closes that window.

The gap between releasing and re-acquiring

A common shape in read-heavy systems: a worker takes a write lock to refresh a cache, then wants to keep reading the value it just wrote. The straightforward version drops the writer and grabs a reader:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::sync::RwLock;

let cache = RwLock::new(0);

let mut w = cache.write().unwrap();
*w = 42;
drop(w); // <-- another writer can grab the lock here

let r = cache.read().unwrap();
assert_eq!(*r, 42);

Between drop(w) and cache.read() the lock is released. On a busy system, another writer can land in that hole and replace your 42 with something else before your reader sees it.

downgrade is atomic

Stabilized in Rust 1.92, RwLockWriteGuard::downgrade consumes the write guard and returns a read guard — no release, no reacquire. The transition is atomic, so no other writer can sneak in:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::sync::{RwLock, RwLockWriteGuard};

let cache = RwLock::new(0);

let mut w = cache.write().unwrap();
*w = 42;

// Atomically: write lock -> read lock. No window.
let r = RwLockWriteGuard::downgrade(w);
assert_eq!(*r, 42);

Other readers waiting on the lock can wake up immediately, while the value you just published is guaranteed to still be 42 when you read it back.

A real shape: refresh-then-publish

The pattern shows up wherever one thread mutates state and then turns into a long-lived reader of the same state — config reloads, cache refreshes, snapshot publishers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use std::sync::{Arc, RwLock, RwLockWriteGuard};
use std::thread;

let snapshot: Arc<RwLock<Vec<u32>>> = Arc::new(RwLock::new(vec![]));

let writer = {
    let snapshot = Arc::clone(&snapshot);
    thread::spawn(move || {
        let mut w = snapshot.write().unwrap();
        w.extend([10, 20, 30]); // expensive build

        // Downgrade so readers can fan in immediately,
        // and so we keep reading the value we just wrote.
        let r = RwLockWriteGuard::downgrade(w);
        r.iter().sum::<u32>()
    })
};

assert_eq!(writer.join().unwrap(), 60);

Without downgrade, you’d either hold the write lock longer than necessary (blocking every reader) or release it and risk reading stale-or-clobbered data.

When to reach for it

Use downgrade whenever a thread finishes writing and immediately wants to read the same RwLock — especially in read-heavy workloads where you want other readers to fan in as soon as possible without losing the consistency of “I’m reading what I just wrote.” If you don’t need the read afterwards, plain drop is fine; if you do, downgrade is the only way to get there without a race.

#124 May 2026

124. Iterator::cycle — Round-Robin Without the Modulo Math

Round-robin assignment usually shows up as things[i % workers.len()] — fine until the index gets clever, the slice gets reordered, or the source isn’t even indexable. Iterator::cycle turns any Clone iterator into an infinite one, and the modulo dance disappears.

The textbook version: distribute jobs across a fixed pool of workers. Indexing works, but you’re carrying the index and the modulo around just to walk in a circle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let workers = ["alice", "bob", "carol"];
let jobs = ["build", "test", "lint", "deploy", "notify"];

// Index-and-modulo: works, but the math is doing the iterating for you.
let mut assigned: Vec<(&str, &str)> = Vec::new();
for (i, job) in jobs.iter().copied().enumerate() {
    assigned.push((job, workers[i % workers.len()]));
}

assert_eq!(
    assigned,
    vec![
        ("build",  "alice"),
        ("test",   "bob"),
        ("lint",   "carol"),
        ("deploy", "alice"),
        ("notify", "bob"),
    ],
);

Swap the modulo for cycle and the loop tells you exactly what it’s doing — pull the next worker, forever:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let workers = ["alice", "bob", "carol"];
let jobs = ["build", "test", "lint", "deploy", "notify"];

let assigned: Vec<(&str, &str)> = jobs
    .iter()
    .copied()
    .zip(workers.iter().copied().cycle())
    .collect();

assert_eq!(
    assigned,
    vec![
        ("build",  "alice"),
        ("test",   "bob"),
        ("lint",   "carol"),
        ("deploy", "alice"),
        ("notify", "bob"),
    ],
);

The trick is zip — zipping a finite iterator with an infinite one stops as soon as the finite side runs out, so you never have to bound cycle yourself. No off-by-one, no bookkeeping for “did I already use this worker?”.

It also composes with take when you want a fixed-length output and the source is the short one:

1
2
3
4
5
let pattern = [1, 2, 3];

let twelve: Vec<i32> = pattern.iter().copied().cycle().take(12).collect();

assert_eq!(twelve, vec![1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]);

A handy companion is enumerate — when you want the round-robin and the original index together:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
let colors = ["red", "green", "blue"];
let rows = ["one", "two", "three", "four", "five"];

let painted: Vec<(usize, &str, &str)> = rows
    .iter()
    .copied()
    .zip(colors.iter().copied().cycle())
    .enumerate()
    .map(|(i, (row, color))| (i, row, color))
    .collect();

assert_eq!(
    painted,
    vec![
        (0, "one",   "red"),
        (1, "two",   "green"),
        (2, "three", "blue"),
        (3, "four",  "red"),
        (4, "five",  "green"),
    ],
);

Two things worth knowing. cycle requires the underlying iterator to be Clone — it remembers the original and starts over each time it’s exhausted, which means it’ll panic or loop forever on an empty iterator depending on what you do with it (zip is safe — empty side wins; bare .next() would just return None forever). And it’s lazy: nothing repeats until the consumer pulls another item, so pairing it with a finite iterator costs nothing extra.

Stable since Rust 1.0 — one of those iterator adapters that makes the modulo operator feel like the wrong tool the moment you remember it exists.

#123 May 2026

123. BTreeMap::pop_first — A Sorted Map That Doubles as a Priority Queue

BinaryHeap only goes one way — biggest first. When you want to pull the smallest or the largest from the same collection, reach for BTreeMap and let pop_first / pop_last do the work.

The classic shape: a queue of jobs keyed by priority where you sometimes need the most-urgent job and sometimes the least-urgent one. With BinaryHeap you’d pick a direction and stick with it (or wrap things in Reverse to flip it). With BTreeMap you get both ends for free, because the keys are already sorted:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use std::collections::BTreeMap;

let mut jobs: BTreeMap<u32, &str> = BTreeMap::new();
jobs.insert(5, "rebuild index");
jobs.insert(1, "send heartbeat");
jobs.insert(9, "page oncall");
jobs.insert(3, "rotate logs");

// Smallest key first — drain by priority.
assert_eq!(jobs.pop_first(), Some((1, "send heartbeat")));
assert_eq!(jobs.pop_first(), Some((3, "rotate logs")));

// Or grab the most urgent from the other end.
assert_eq!(jobs.pop_last(), Some((9, "page oncall")));

// Empty? You get None — same shape as Vec::pop.
let mut empty: BTreeMap<u32, &str> = BTreeMap::new();
assert_eq!(empty.pop_first(), None);

Both methods return Option<(K, V)> and remove the entry from the map. No second lookup, no .remove(key) follow-up after .first_key_value().

Where this really earns its keep is the “drain-in-order” loop — the kind of thing you’d otherwise write with a heap plus a sidecar map:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use std::collections::BTreeMap;

let mut tasks: BTreeMap<u32, String> = BTreeMap::new();
tasks.insert(20, "compact".into());
tasks.insert(10, "vacuum".into());
tasks.insert(30, "snapshot".into());

let mut order = Vec::new();
while let Some((priority, name)) = tasks.pop_first() {
    order.push((priority, name));
}

assert_eq!(
    order,
    vec![
        (10, "vacuum".into()),
        (20, "compact".into()),
        (30, "snapshot".into()),
    ],
);

Same loop, swap pop_first for pop_last and you drain in reverse order — no Reverse wrapper, no second collection.

BTreeSet got the same pair (pop_first / pop_last) at the same time, so a sorted set behaves like a deque you can pop from either end:

1
2
3
4
5
6
use std::collections::BTreeSet;

let mut ids: BTreeSet<u32> = BTreeSet::from([7, 2, 9, 4]);
assert_eq!(ids.pop_first(), Some(2));
assert_eq!(ids.pop_last(),  Some(9));
assert_eq!(ids.len(), 2);

A few things worth knowing. BTreeMap insertion is O(log n) — heavier than a BinaryHeap push, which amortises to O(1). If you genuinely only ever pop from one side and throughput matters, a heap still wins. The moment you need ordered iteration, range queries, or popping from both ends, BTreeMap is the better fit and pop_first / pop_last make that fit feel native.

Stable since Rust 1.66 — and one of those methods that quietly replaces a fistful of match arms once you remember it exists.

#122 May 2026

122. Option::filter — Keep Some Only When the Value Passes

You’ve got an Option<T>, but you only want to keep the Some if the value passes a test. The match-with-guard version works — Option::filter says the same thing in one call.

The shape that keeps showing up: parse something into an Option, then validate it. The naive version stacks an if on top of the unwrap:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn parse_port(raw: Option<&str>) -> Option<u16> {
    let s = raw?;
    let n: u16 = s.parse().ok()?;
    if n > 0 { Some(n) } else { None }
}

assert_eq!(parse_port(Some("8080")), Some(8080));
assert_eq!(parse_port(Some("0")),    None);
assert_eq!(parse_port(Some("nope")), None);
assert_eq!(parse_port(None),         None);

Option::filter collapses that trailing if into the chain:

1
2
3
4
5
6
7
fn parse_port(raw: Option<&str>) -> Option<u16> {
    raw.and_then(|s| s.parse().ok())
       .filter(|&n| n > 0)
}

assert_eq!(parse_port(Some("8080")), Some(8080));
assert_eq!(parse_port(Some("0")),    None);

Same story for the match-with-guard pattern — when the predicate is the only thing the arm checks, filter reads better:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let name: Option<&str> = Some("");

// Before: pattern match with a guard
let valid = match name {
    Some(s) if !s.is_empty() => Some(s),
    _ => None,
};
assert_eq!(valid, None);

// After: just say what you mean
let valid = name.filter(|s| !s.is_empty());
assert_eq!(valid, None);

Two things worth knowing. First, the closure receives &T, not T — same as Iterator::filter. So for Option<i32> you write |&n| n > 0 or |n| *n > 0. For Option<String> auto-deref makes |s| !s.is_empty() just work.

Second, filter only keeps or drops — it never transforms. If the predicate returns true you get the original Some back, untouched. To transform, chain .map() after:

1
2
3
4
let trimmed = Some("  hello  ")
    .map(str::trim)
    .filter(|s| !s.is_empty());
assert_eq!(trimmed, Some("hello"));

Stable since Rust 1.27, and the kind of method that quietly disappears the boilerplate once you know it’s there.

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

#120 May 2026

120. OnceLock — Lazy Statics That Initialize on Your Schedule

LazyLock runs its initializer the first time anyone touches the value — fine when the inputs are baked in at compile time, useless when you only learn them at runtime. OnceLock is the same idea, but you decide when (and with what data) initialization happens.

The classic case: you want a global that’s expensive to build, and the data only exists after main starts — CLI args, env vars, a parsed config file. LazyLock can’t see those without baking the work into a closure that re-runs every test setup.

OnceLock solves it by separating creation from initialization:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::sync::OnceLock;

static CONFIG: OnceLock<String> = OnceLock::new();

fn main() {
    // Initialize from real runtime data, exactly once.
    let cfg = std::env::var("APP_CONFIG").unwrap_or_else(|_| "default".into());
    CONFIG.set(cfg).unwrap();

    assert_eq!(config(), "default");
}

fn config() -> &'static str {
    CONFIG.get().expect("config not initialized")
}

set returns Err if the cell was already filled — you get to decide whether that’s a panic, a log line, or a no-op.

For the read-mostly path, get_or_init combines “is it set?” and “set it” into a single thread-safe call. Concurrent callers race; the winner runs the closure, everyone else waits and reads the result:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::sync::OnceLock;

static GREETING: OnceLock<String> = OnceLock::new();

fn greeting() -> &'static str {
    GREETING.get_or_init(|| format!("hello, {}", "world"))
}

assert_eq!(greeting(), "hello, world");
assert_eq!(greeting(), "hello, world"); // cached, closure does not run again

When to reach for which: pick LazyLock when the initializer is self-contained and you’re happy with it firing on first touch. Pick OnceLock when you need to feed in runtime data — or when you want the option to ask “has this been set yet?” before triggering the work.

#119 May 2026

119. iter::from_fn — Build a Custom Iterator Without the Struct

Need a custom iterator that carries a bit of state? Skip the struct plus impl Iterator boilerplate. iter::from_fn turns any closure that returns Option<T> into a real iterator.

The problem

You want a custom iterator with some captured state — a counter, a parser cursor, a lazy generator. The textbook approach is a struct plus a manual Iterator impl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
struct Counter { n: u32, max: u32 }

impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if self.n < self.max {
            self.n += 1;
            Some(self.n)
        } else {
            None
        }
    }
}

let counter = Counter { n: 0, max: 5 };
let v: Vec<u32> = counter.collect();
assert_eq!(v, vec![1, 2, 3, 4, 5]);

Three lines of logic, ten lines of scaffolding.

The fix

std::iter::from_fn takes a FnMut() -> Option<T> and returns an iterator. Yield Some(x) for each item, None to stop. The closure’s captured variables become your iterator state:

1
2
3
4
5
6
7
8
use std::iter;

let mut n = 0;
let v: Vec<u32> = iter::from_fn(|| {
    n += 1;
    (n <= 5).then_some(n)
}).collect();
assert_eq!(v, vec![1, 2, 3, 4, 5]);

The closure captures n mutably and updates it on every call. No struct, no impl, no type to name.

Where it shines: tiny tokenizers

from_fn really earns its keep when paired with Peekable::next_if. Pull characters until a condition fails and you have a one-liner tokenizer:

1
2
3
4
5
6
7
8
let mut chars = "123abc".chars().peekable();
let digits: String =
    iter::from_fn(|| chars.next_if(|c| c.is_ascii_digit())).collect();
assert_eq!(digits, "123");

// chars still has "abc" left to consume
let rest: String = chars.collect();
assert_eq!(rest, "abc");

Reach for from_fn whenever the iterator’s state would just be a couple of local variables. Reach for a manual impl Iterator when the iterator is a public type, needs to implement other traits, or you want a size hint and specialization.

#118 May 2026

118. [T; N]::map — Transform an Array Without Allocating a Vec

[1, 2, 3].iter().map(|n| n * 2).collect::<Vec<_>>() works, but you’ve thrown the length away in the type and paid for a heap allocation. Arrays have their own map — same shape in, same shape out, no Vec in sight.

The reflex for transforming an array is the iterator chain:

1
2
3
let nums = [1, 2, 3, 4];
let doubled: Vec<i32> = nums.iter().map(|n| n * 2).collect();
assert_eq!(doubled, vec![2, 4, 6, 8]);

That gives you a Vec<i32>. The compiler no longer knows the length, and you allocated on the heap to find that out. If you want the array shape back, you’re stuck with try_into and a unwrap you don’t want.

[T; N]::map skips all of it. The output is [U; N] — same N, brand-new element type:

1
2
3
let nums = [1, 2, 3, 4];
let doubled: [i32; 4] = nums.map(|n| n * 2);
assert_eq!(doubled, [2, 4, 6, 8]);

No heap, no length erased, no try_into. Just an array on the stack with a different element type.

It takes each element by value, so it works fine with non-Copy types — no clone dance:

1
2
3
let names = [String::from("a"), String::from("bb"), String::from("ccc")];
let lens: [usize; 3] = names.map(|s| s.len());
assert_eq!(lens, [1, 2, 3]);

The closure consumes the String, the array is moved, and you get a fresh [usize; 3] back. Compare to the iterator version, which would need .into_iter() plus a try_into to recover the array type.

It’s also a clean way to build initialized arrays from one you already have — RGB to RGBA, raw bytes to parsed records, anything fixed-width:

1
2
3
4
5
6
let rgb: [u8; 3] = [200, 100, 50];
let rgba: [u8; 4] = {
    let [r, g, b] = rgb.map(|c| c.saturating_add(5));
    [r, g, b, 255]
};
assert_eq!(rgba, [205, 105, 55, 255]);

When you genuinely want a Vec, .iter().map().collect() still wins. But when the length is part of the design — config slots, fixed-N pipelines, embedded buffers, no_std code — [T; N]::map keeps that fact in the type system instead of throwing it away.

#117 May 2026

117. Iterator::step_by — Every Nth Element Without filter + enumerate

Want every 3rd value from a series? The reflex is enumerate().filter(|(i, _)| i % 3 == 0) — three combinators, one modulo, and you’ve thrown away the indices anyway. step_by(3) does the same thing in one call.

The classic shape: keep every Nth item, drop the rest. Most people reach for enumerate plus a modulo filter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let xs = [10, 20, 30, 40, 50, 60, 70];

let evens_by_index: Vec<_> = xs
    .iter()
    .enumerate()
    .filter(|(i, _)| i % 2 == 0)
    .map(|(_, x)| *x)
    .collect();

assert_eq!(evens_by_index, [10, 30, 50, 70]);

That works, but you’re indexing just to throw the index away, and the filter runs once per element even though the iterator already knows where to land.

Iterator::step_by(n) yields the first item, then advances by n - 1, repeating. Same result, no bookkeeping:

1
2
3
4
5
let xs = [10, 20, 30, 40, 50, 60, 70];

let stepped: Vec<_> = xs.iter().step_by(2).copied().collect();

assert_eq!(stepped, [10, 30, 50, 70]);

The first element is always included — step_by(n) starts at index 0, then jumps. If you want to skip the first one, chain with skip:

1
2
3
4
5
let xs = [10, 20, 30, 40, 50, 60, 70];

let from_second: Vec<_> = xs.iter().skip(1).step_by(2).copied().collect();

assert_eq!(from_second, [20, 40, 60]);

It composes nicely with ranges, which is where it really shines — multiples, downsampling, every-other-frame logic without writing the loop yourself:

1
2
3
4
5
6
7
8
// All multiples of 5 up to 30 (inclusive)
let multiples: Vec<i32> = (0..=30).step_by(5).collect();
assert_eq!(multiples, [0, 5, 10, 15, 20, 25, 30]);

// Downsample a buffer to one in four
let signal: Vec<f32> = (0..16).map(|i| i as f32).collect();
let downsampled: Vec<f32> = signal.iter().step_by(4).copied().collect();
assert_eq!(downsampled, [0.0, 4.0, 8.0, 12.0]);

One footgun: step_by(0) panics. The step has to be at least 1, which makes sense — you can’t “advance by zero” and make progress — but it’s a runtime panic, not a compile error, so don’t pass a step you computed at runtime without checking.

1
2
3
4
5
6
7
8
// This would panic: (0..10).step_by(0)
fn safe_step(xs: &[i32], n: usize) -> Vec<i32> {
    if n == 0 { return Vec::new(); }
    xs.iter().step_by(n).copied().collect()
}

assert_eq!(safe_step(&[1, 2, 3, 4], 0), Vec::<i32>::new());
assert_eq!(safe_step(&[1, 2, 3, 4], 2), vec![1, 3]);

Reach for step_by whenever you’d otherwise write enumerate().filter(|(i, _)| i % n == 0) — same behavior, half the code, and the iterator can actually skip elements instead of inspecting every one.

#116 May 2026

116. Path::file_prefix — Get the Real Stem of archive.tar.gz

Path::file_stem strips the last extension, so archive.tar.gz comes back as archive.tar. That’s almost never what you want for double-extension files. file_prefix strips from the first dot instead — archive, finally.

The classic confusion. You ask for the “stem” of a tarball and get something with .tar still glued on:

1
2
3
4
5
6
use std::path::Path;

let p = Path::new("backups/archive.tar.gz");

assert_eq!(p.file_stem(),   Some("archive.tar".as_ref()));
assert_eq!(p.extension(),   Some("gz".as_ref()));

file_stem takes the file name and drops everything from the last . onwards. For a single extension that’s fine. For .tar.gz, .min.js, .d.ts, .spec.ts, you end up doing the second strip yourself:

1
2
3
4
5
6
7
8
9
use std::path::Path;

fn real_stem_old(p: &Path) -> Option<&str> {
    let stem = p.file_stem()?.to_str()?;
    Some(stem.split('.').next().unwrap_or(stem))
}

assert_eq!(real_stem_old(Path::new("archive.tar.gz")), Some("archive"));
assert_eq!(real_stem_old(Path::new("bundle.min.js")),  Some("bundle"));

Works, but you’ve left OsStr land just to do a string split, and you’ve quietly made the function lossy on non-UTF-8 paths.

Rust 1.91 stabilised Path::file_prefix. It returns the file name up to the first . — staying in OsStr the whole time:

1
2
3
4
5
6
use std::path::Path;

assert_eq!(Path::new("archive.tar.gz").file_prefix(), Some("archive".as_ref()));
assert_eq!(Path::new("bundle.min.js").file_prefix(),  Some("bundle".as_ref()));
assert_eq!(Path::new("notes.md").file_prefix(),       Some("notes".as_ref()));
assert_eq!(Path::new("README").file_prefix(),         Some("README".as_ref()));

Leading dots on dotfiles are kept — exactly like file_stem already does — so you don’t accidentally turn .bashrc into an empty string:

1
2
3
4
use std::path::Path;

assert_eq!(Path::new(".bashrc").file_prefix(),     Some(".bashrc".as_ref()));
assert_eq!(Path::new(".config.toml").file_prefix(), Some(".config".as_ref()));

Pair it with file_stem when you want both halves of a multi-extension name in one place:

1
2
3
4
5
6
7
8
use std::path::Path;

let p = Path::new("logs/app.2026-05-03.log.gz");
let prefix = p.file_prefix().and_then(|s| s.to_str()).unwrap_or("");
let stem   = p.file_stem().and_then(|s| s.to_str()).unwrap_or("");

assert_eq!(prefix, "app");                     // the real name
assert_eq!(stem,   "app.2026-05-03.log");      // everything except the final ext

Reach for file_prefix whenever a filename has more than one dot and you want the part a human would call “the name”.

#115 May 2026

115. Vec::resize_with — Grow a Vec With a Closure, Not a Clone

Vec::resize makes every new slot a clone of the same value. When you need fresh values per slot — counters, allocations, defaults — resize_with calls a closure for each new element instead.

Vec::resize(n, value) is fine when the filler is cheap and identical, but it has two annoyances. It needs T: Clone, and every new slot is the same clone. So this doesn’t work the way you want:

1
2
3
4
5
// One Vec shared across every slot — mutating slot 0 mutates them all.
let mut grid: Vec<Vec<u8>> = Vec::new();
grid.resize(3, Vec::new());
grid[0].push(42);
assert_eq!(grid[1], vec![]); // fine — Vec::new() clones to a new empty Vec

That one happens to be safe because Vec::clone actually allocates. But the moment your T is Rc<RefCell<…>>, every slot points at the same cell. And if T isn’t Clone at all, you can’t call resize in the first place.

resize_with takes a closure and calls it once per new slot:

1
2
3
4
5
6
7
let mut counter = 0;
let mut v = vec![10, 20];
v.resize_with(5, || {
    counter += 1;
    counter
});
assert_eq!(v, vec![10, 20, 1, 2, 3]);

The closure can capture mutable state, so each call is fresh. Generating IDs, pulling from an RNG, allocating independent buffers — all easy:

1
2
3
4
5
6
7
8
9
let mut next_id = 100;
let mut buffers: Vec<(usize, Vec<u8>)> = Vec::new();
buffers.resize_with(3, || {
    let id = next_id;
    next_id += 1;
    (id, Vec::with_capacity(1024))
});
assert_eq!(buffers[0].0, 100);
assert_eq!(buffers[2].0, 102);

For non-Clone types, Default::default is the usual filler:

1
2
3
4
5
6
7
8
9
#[derive(Default, Debug, PartialEq)]
struct Slot {
    open: bool,
    payload: Vec<u8>,
}

let mut slots: Vec<Slot> = Vec::new();
slots.resize_with(2, Default::default);
assert_eq!(slots, vec![Slot::default(), Slot::default()]);

Shrinking still works, and the closure is never called when the new length is smaller:

1
2
3
let mut v = vec![1, 2, 3, 4, 5];
v.resize_with(2, || unreachable!());
assert_eq!(v, vec![1, 2]);

Reach for resize_with whenever the filler isn’t a single static value — and especially when T doesn’t (or shouldn’t) implement Clone.

114. Option::transpose — Use ? on an Optional Result

Got an Option<Result<T, E>> and want to ? the error out? You can’t — ? doesn’t reach inside the Option. transpose flips it to Result<Option<T>, E>, and the rest takes care of itself.

The classic case: a config field that’s optional, but if it’s there, it has to parse. The old dance is three match arms just to thread the error out of the Option:

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

fn parse_port_old(raw: Option<&str>) -> Result<Option<u16>, ParseIntError> {
    match raw.map(str::parse::<u16>) {
        Some(Ok(p))  => Ok(Some(p)),
        Some(Err(e)) => Err(e),
        None         => Ok(None),
    }
}

transpose collapses it:

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

fn parse_port(raw: Option<&str>) -> Result<Option<u16>, ParseIntError> {
    raw.map(str::parse::<u16>).transpose()
}

Option<Result<T, E>>::transpose() returns Result<Option<T>, E> — exactly the shape ? wants. Now you can chain it inline:

1
2
3
4
5
6
7
use std::collections::HashMap;
use std::num::ParseIntError;

fn read_port(config: &HashMap<&str, &str>) -> Result<Option<u16>, ParseIntError> {
    let port = config.get("port").copied().map(str::parse::<u16>).transpose()?;
    Ok(port)
}

It works the other way too: Result<Option<T>, E>::transpose() returns Option<Result<T, E>>. Handy when an iterator chain wants the Option on the outside.

All three cases, one call:

1
2
3
assert_eq!(parse_port(Some("8080")).unwrap(), Some(8080));
assert_eq!(parse_port(None).unwrap(), None);
assert!(parse_port(Some("nope")).is_err());

Reach for transpose any time Option and Result get nested and you wish ? could see through both.

#113 May 2026

113. Arc::make_mut — Mutate Inside an Arc Without the Dance

You have an Arc<T>, you want a &mut T. Arc only hands out &T, so the usual workaround is clone-the-inner, mutate, rewrap. Arc::make_mut does that for you — and skips the clone when no one else is watching.

The manual version everyone writes once and then copies forever:

1
2
3
4
5
6
7
8
9
use std::sync::Arc;

let mut shared = Arc::new(vec![1, 2, 3]);

let mut owned: Vec<i32> = (*shared).clone(); // always clones
owned.push(4);
shared = Arc::new(owned);                    // always reallocates the Arc

assert_eq!(*shared, vec![1, 2, 3, 4]);

It works, but it clones the Vec and reallocates the Arc every single time — even when this Arc is the only one pointing at the data.

Arc::make_mut takes &mut Arc<T> and hands you &mut T:

1
2
3
4
5
6
7
use std::sync::Arc;

let mut shared = Arc::new(vec![1, 2, 3]);

Arc::make_mut(&mut shared).push(4);

assert_eq!(*shared, vec![1, 2, 3, 4]);

One call, one borrow, and — crucially — no clone when this Arc is unique:

1
2
3
4
5
use std::sync::Arc;

let mut solo = Arc::new(vec![1, 2, 3]);
Arc::make_mut(&mut solo).push(99); // strong_count == 1, mutates in place
assert_eq!(*solo, vec![1, 2, 3, 99]);

When the Arc is shared, make_mut quietly clones the inner value into a fresh allocation and detaches your handle from the rest. The other handles keep seeing the old data — clone-on-write, exactly like you’d want:

1
2
3
4
5
6
7
8
9
use std::sync::Arc;

let mut a = Arc::new(vec![1, 2, 3]);
let b = Arc::clone(&a);            // strong_count == 2

Arc::make_mut(&mut a).push(99);    // clones, then mutates the clone

assert_eq!(*a, vec![1, 2, 3, 99]); // a moved to its own allocation
assert_eq!(*b, vec![1, 2, 3]);     // b still sees the original

The same method exists on Rc for single-threaded code, with identical semantics. Reach for make_mut whenever you find yourself cloning the inside of an Arc just to change one field — you’ll skip the allocation in the common case and get an honest &mut T in return.

#112 May 2026

112. Iterator::scan — Fold That Yields Every Step

fold keeps the running state but only hands you the final answer. So you reach for a mut variable plus map, or you give up and collect first. scan is the missing middle: a fold that yields each intermediate step.

The setup is familiar — you want a running total, not just the sum:

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

let mut sum = 0;
let totals: Vec<i32> = nums.iter()
    .map(|&x| { sum += x; sum })
    .collect();
assert_eq!(totals, vec![1, 3, 6, 10, 15]);

It works, but the state lives outside the chain. map is supposed to be pure — leaning on a captured mut makes the iterator harder to refactor and impossible to compose.

scan puts the state inside the chain. You hand it an initial value and a closure that mutates the state and returns each yielded item:

1
2
3
4
5
6
7
8
9
let nums = [1, 2, 3, 4, 5];

let totals: Vec<i32> = nums.iter()
    .scan(0, |sum, &x| {
        *sum += x;
        Some(*sum)
    })
    .collect();
assert_eq!(totals, vec![1, 3, 6, 10, 15]);

No captured state, no second pass. The closure returns Option<U>, so returning None ends the iteration early — handy for “stop when the running total crosses a threshold”:

1
2
3
4
5
6
7
8
9
let nums = [3, 4, 5, 6, 7];

let until_ten: Vec<i32> = nums.iter()
    .scan(0, |sum, &x| {
        *sum += x;
        (*sum <= 10).then_some(*sum)
    })
    .collect();
assert_eq!(until_ten, vec![3, 7]);

The state isn’t limited to numbers either — pair it with a tuple to track “previous + current” and you’ve got differences in one pass:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let readings = [10, 13, 12, 18, 20];

let deltas: Vec<i32> = readings.iter()
    .scan(None, |prev, &x| {
        let d = prev.map(|p| x - p);
        *prev = Some(x);
        Some(d)
    })
    .flatten()
    .collect();
assert_eq!(deltas, vec![3, -1, 6, 2]);

Reach for scan whenever you’d otherwise write let mut acc = … outside a map. Same shape, no escapee state.

#111 Apr 2026

111. Vec::insert_mut — Splice In and Edit Without Reindexing

You insert a placeholder, then index back to fix it up. Two lookups, two bounds checks, one wobble. Vec::insert_mut — stable in 1.95 — hands you the &mut T directly.

The classic dance:

1
2
3
4
let mut v = vec![1, 2, 4, 5];
v.insert(2, 0);
v[2] = 3; // recompute the index, second bounds check
assert_eq!(v, [1, 2, 3, 4, 5]);

insert_mut returns the slot:

1
2
3
4
let mut v = vec![1, 2, 4, 5];
let slot = v.insert_mut(2, 0);
*slot = 3;
assert_eq!(v, [1, 2, 3, 4, 5]);

This shines when the value is built in pieces — push a default, then fill it in based on where it landed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#[derive(Default, Debug, PartialEq)]
struct Job { id: u32, label: String }

let mut jobs: Vec<Job> = vec![Job { id: 1, label: "build".into() }];
let j = jobs.insert_mut(0, Job::default());
j.id = 0;
j.label = "setup".into();

assert_eq!(jobs[0], Job { id: 0, label: String::from("setup") });
assert_eq!(jobs[1].label, "build");

Rust 1.95 grew the whole _mut family alongside Vec::push_mut: Vec::insert_mut, VecDeque::push_front_mut, VecDeque::push_back_mut, VecDeque::insert_mut, LinkedList::push_front_mut, and LinkedList::push_back_mut. Same idea everywhere — the place-it method now returns a mutable reference to the slot it just placed.

1
2
3
4
5
6
use std::collections::VecDeque;

let mut q: VecDeque<i32> = VecDeque::from([2, 3]);
let head = q.push_front_mut(0);
*head += 1; // now 1
assert_eq!(q, VecDeque::from([1, 2, 3]));

Quietly useful, no churn — just fewer indices floating around.

#110 Apr 2026

110. slice::split_at_checked — Split Without the Panic

slice.split_at(i) panics the second i > len. The usual fix is a length check wrapped around the call so you don’t blow up on a bad index. split_at_checked does the same job in one call and hands you an Option.

The classic trap — a single bad index away from a panic:

1
2
let xs = [1, 2, 3, 4];
let (head, tail) = xs.split_at(10); // panics: byte index 10 is out of bounds

The defensive version everyone writes:

1
2
3
4
5
6
7
8
9
let xs = [1, 2, 3, 4];
let i = 10;

if i <= xs.len() {
    let (head, tail) = xs.split_at(i);
    // ...use head and tail
} else {
    // handle out-of-bounds
}

Two reads of i, one easy off-by-one (< vs <=), and a panic waiting if you ever drop the guard.

Rust 1.80 stabilised split_at_checked (and split_at_mut_checked), which folds the bounds check into the return type:

1
2
3
4
5
let xs = [1, 2, 3, 4];

assert_eq!(xs.split_at_checked(2), Some((&xs[..2], &xs[2..])));
assert_eq!(xs.split_at_checked(4), Some((&xs[..], &[][..]))); // boundary is fine
assert_eq!(xs.split_at_checked(5), None);                     // would have panicked

Now the bounds check is the API. You get an Option<(&[T], &[T])> and the compiler nudges you to handle the None case:

1
2
3
4
5
6
7
fn take_prefix(buf: &[u8], n: usize) -> Option<&[u8]> {
    let (head, _rest) = buf.split_at_checked(n)?;
    Some(head)
}

assert_eq!(take_prefix(b"hello", 3), Some(&b"hel"[..]));
assert_eq!(take_prefix(b"hi", 3), None);

? does the bailout, no manual length check, no panic path. This works on &str too, where the index has to land on a UTF-8 boundary — and it returns None if it doesn’t, instead of panicking.

109. BinaryHeap::peek_mut — Edit the Top Without Pop-and-Push

Updating the largest element in a BinaryHeap shouldn’t take two heap operations. peek_mut lets you mutate it in place and re-heapifies on drop — one sift instead of two.

The pop-and-push dance

Say you keep tasks in a max-heap by priority and you need to bump the top one down. The naive recipe is to pop, change it, and push it back:

1
2
3
4
5
6
7
8
9
use std::collections::BinaryHeap;

let mut heap = BinaryHeap::from([3, 1, 5, 2, 4]);

// Pull the max out, edit it, push it back.
let top = heap.pop().unwrap();
heap.push(top - 10);

assert_eq!(heap.peek(), Some(&4));

That’s two O(log n) heap operations — a sift-down for pop, a sift-up for push — plus an awkward two-step where the heap briefly forgets it ever had a max.

peek_mut does it in one shot

peek_mut returns a PeekMut guard that derefs to the top element. You mutate it in place, and the heap fixes itself with a single sift-down when the guard is dropped:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::collections::BinaryHeap;

let mut heap = BinaryHeap::from([3, 1, 5, 2, 4]);

if let Some(mut top) = heap.peek_mut() {
    *top -= 10; // 5 becomes -5
}
// PeekMut dropped here — heap re-heapifies once.

assert_eq!(heap.peek(), Some(&4));

One traversal, no temporary owned value, and the heap invariant is restored before the next line of code runs.

Conditionally pop without a re-peek

PeekMut also has an associated pop function. Look at the top, decide whether to keep or remove it, all without peeking twice:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::collections::{BinaryHeap, binary_heap::PeekMut};

let mut heap = BinaryHeap::from([10, 7, 4, 1]);

if let Some(top) = heap.peek_mut() {
    if *top > 5 {
        // Pop only when the top passes a check.
        PeekMut::pop(top);
    }
}

assert_eq!(heap.peek(), Some(&7));

Same shape as Vec::pop_if, but for the heap’s max — pull the top out only when it actually meets your condition.

When to reach for it

Any time you’d write pop().push() to edit the max, peek_mut is the better tool: one heap fixup instead of two, and the borrow stays inside the heap so you don’t shuffle ownership for nothing. Great fit for priority queues that adjust the top entry — schedulers, event loops, top-k trackers.

#108 Apr 2026

108. Iterator::max_by_key — Find the Biggest Without Sorting the Whole Thing

Need the longest string in a Vec? Don’t sort the whole list to grab the last element — max_by_key walks once, allocates nothing, and returns it directly.

The wasteful way

You want the longest word from a list. The first instinct is often to sort and pick:

1
2
3
4
5
6
7
let mut words = vec!["fig", "apple", "kiwi", "blackberry", "pear"];

// Sort the full vec by length, then grab the last element.
words.sort_by_key(|w| w.len());
let longest = words.last().unwrap();

assert_eq!(*longest, "blackberry");

That’s O(n log n) work — and a side effect, since your Vec is now reordered — just to answer “which one is biggest?”.

Enter max_by_key

max_by_key walks the iterator once, tracks the running maximum, and hands you the element it picked:

1
2
3
4
5
let words = vec!["fig", "apple", "kiwi", "blackberry", "pear"];

let longest = words.iter().max_by_key(|w| w.len()).unwrap();

assert_eq!(*longest, "blackberry");

O(n), no allocation, no mutation. The original Vec is untouched and you get a &&str back without cloning anything.

Same energy: min_by_key

The mirror method is just as useful — find the shortest word in one pass:

1
2
3
4
5
let words = vec!["fig", "apple", "kiwi", "blackberry", "pear"];

let shortest = words.iter().min_by_key(|w| w.len()).unwrap();

assert_eq!(*shortest, "fig");

Picking by computed value

The closure can do real work — compute distance, score, age — anything that returns something Ord:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct Player { name: &'static str, score: u32 }

let roster = [
    Player { name: "Ferris", score: 80 },
    Player { name: "Corro",  score: 95 },
    Player { name: "Crusty", score: 60 },
];

let top = roster.iter().max_by_key(|p| p.score).unwrap();

assert_eq!(top.name, "Corro");

No need to sort, no need to implement Ord on the struct — just point at the field that decides.

Tiebreaks: last one wins

When two elements share the same key, max_by_key returns the last one and min_by_key returns the first:

1
2
3
4
5
6
7
let v = vec![(1, 'a'), (3, 'b'), (3, 'c'), (2, 'd')];

let max = v.iter().max_by_key(|(k, _)| *k).unwrap();
let min = v.iter().min_by_key(|(k, _)| *k).unwrap();

assert_eq!(*max, (3, 'c')); // last 3
assert_eq!(*min, (1, 'a')); // first 1

Worth knowing if your data has duplicates — pick the iteration direction so the right element wins.

When to reach for it

If you catch yourself sorting and then grabbing first(), last(), or [0], stop. min_by_key and max_by_key are the targeted tools: one pass, zero allocations, and the result type is exactly the element you wanted.

#107 Apr 2026

107. Vec::splice — Replace a Range and Keep the Old Values

Need to swap a chunk of a Vec for a different number of items? Most people reach for drain plus extend and a bunch of bookkeeping. Vec::splice does the whole thing in one call — and even hands back what it removed.

The clunky way

You want to replace v[1..4] with two different values. Without splice, you end up juggling indices:

1
2
3
4
5
6
7
8
9
let mut v = vec![1, 2, 3, 4, 5];

// Remove the middle, save the tail, then stitch it back together.
let tail: Vec<_> = v.drain(4..).collect();
v.truncate(1);
v.extend([10, 20]);
v.extend(tail);

assert_eq!(v, [1, 10, 20, 5]);

Three steps, one temporary Vec, and you didn’t even capture what was removed. Easy to off‑by‑one, easy to get wrong.

Enter splice

splice takes a range and an iterator and swaps them in place — the replacement can be any length:

1
2
3
4
5
let mut v = vec![1, 2, 3, 4, 5];

v.splice(1..4, [10, 20]);

assert_eq!(v, [1, 10, 20, 5]);

One call. No tail buffer. Works whether the replacement is shorter, longer, or the same size as the range.

It returns the removed items too

splice is a draining iterator — collect it and you get the elements that were taken out:

1
2
3
4
5
6
let mut v = vec![10, 20, 30, 40, 50];

let removed: Vec<_> = v.splice(1..4, [99]).collect();

assert_eq!(removed, [20, 30, 40]);
assert_eq!(v, [10, 99, 50]);

Perfect for “swap this section, but undo it later” patterns.

Insert without removing

Pass an empty range and splice becomes a multi‑item insert:

1
2
3
4
5
let mut v = vec![1, 5];

v.splice(1..1, [2, 3, 4]);

assert_eq!(v, [1, 2, 3, 4, 5]);

That’s far nicer than calling insert in a loop and watching every push shift the tail again.

When to reach for it

Any time you’d write drain(..) followed by extend(..) or a tower of insert calls, try splice first. One method, one allocation pattern, and the removed items come along for free.

#106 Apr 2026

106. Iterator::by_ref — Take Part of an Iterator and Keep the Rest

Called iter.take(n).collect() and watched the borrow checker swallow the whole iterator? by_ref lets you consume a chunk and keep iterating from where you stopped.

The trap: adapters consume their iterator

Most iterator adapters take self, not &mut self. The moment you write iter.take(2), you have moved iter into the new Take adapter. The original binding is gone:

1
2
3
4
5
6
let v = vec![1, 2, 3, 4, 5];
let mut iter = v.into_iter();

let _first_two: Vec<i32> = iter.take(2).collect();
// let rest: Vec<i32> = iter.collect();
// ^^ error: use of moved value: `iter`

So even though there are clearly elements left, the variable that points at them is no longer usable.

The fix: iter.by_ref()

Iterator::by_ref returns a &mut Self — and &mut I itself implements Iterator whenever I: Iterator. Adapters chained off by_ref() consume from the underlying iterator without moving it:

1
2
3
4
5
6
7
8
let v = vec![1, 2, 3, 4, 5];
let mut iter = v.into_iter();

let first_two: Vec<i32> = iter.by_ref().take(2).collect();
let rest: Vec<i32> = iter.collect();

assert_eq!(first_two, [1, 2]);
assert_eq!(rest, [3, 4, 5]);

Same iterator, two phases, no clones, no indexing.

A real use: parse a header, then a body

The pattern shines when the front of a stream uses different rules than the rest. Pull lines until you hit a blank one, then hand the same iterator to the body parser:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let input = "Subject: hi\nFrom: a@b\n\nbody line 1\nbody line 2";
let mut lines = input.lines();

let headers: Vec<&str> = lines
    .by_ref()
    .take_while(|l| !l.is_empty())
    .collect();

let body: Vec<&str> = lines.collect();

assert_eq!(headers, ["Subject: hi", "From: a@b"]);
assert_eq!(body, ["body line 1", "body line 2"]);

Without by_ref, take_while would have consumed lines whole and the body would be unreachable.

Gotcha: take_while peeks one past

take_while reads the first non-matching element to know when to stop — and that element is gone from the underlying iterator. With by_ref you’ll see this directly:

1
2
3
4
5
6
7
let mut nums = (1..).into_iter();

let small: Vec<i32> = nums.by_ref().take_while(|&n| n < 3).collect();
let next = nums.next();

assert_eq!(small, [1, 2]);
assert_eq!(next, Some(4)); // 3 was eaten by take_while

If you need to keep the boundary element, reach for Peekable instead.

When to use it

Any time you want to apply an adapter to part of an iterator and continue afterwards: chunked parsing, header/body splits, “take the first N then process the rest”, consuming until a sentinel. by_ref() turns “I moved my iterator and now I can’t use it” into a single extra method call.

#105 Apr 2026

105. Vec::extend_from_within — Duplicate a Range Without Fighting the Borrow Checker

Trying to copy a slice of a Vec back onto the end of itself? v.extend_from_slice(&v[..k]) won’t compile — &v and &mut v collide. extend_from_within is the one-call answer.

The borrow checker says no

It looks innocent: take the first few bytes of a buffer and append them to the end. The naïve write doesn’t even reach the type checker without complaint:

1
2
3
let mut buf: Vec<u8> = vec![0xCA, 0xFE, 0xBA, 0xBE];
// buf.extend_from_slice(&buf[..2]);
// ^^ error: cannot borrow `buf` as immutable because it is also borrowed as mutable

extend_from_slice needs &mut self, and &buf[..2] is an immutable borrow of the same Vec. Two borrows, one of them mutable, same value — instant rejection.

The usual workarounds bloat the call site. Either make a temporary copy, or reach for unsafe and a raw pointer dance:

1
2
3
4
5
6
let mut buf: Vec<u8> = vec![0xCA, 0xFE, 0xBA, 0xBE];

let head: Vec<u8> = buf[..2].to_vec();   // extra allocation
buf.extend_from_slice(&head);

assert_eq!(buf, [0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE]);

A whole heap allocation just to copy two bytes inside a vector you already own. There’s a better way.

extend_from_within — one call, no temp

Vec::extend_from_within takes a range into self and appends those elements. No second buffer, no unsafe, no fight with the borrow checker:

1
2
3
4
let mut buf: Vec<u8> = vec![0xCA, 0xFE, 0xBA, 0xBE];
buf.extend_from_within(..2);

assert_eq!(buf, [0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE]);

It accepts any RangeBounds<usize>..k, i..j, i..=j, .. — so you can grab whatever slice you want, including the whole Vec:

1
2
3
4
let mut v = vec![1, 2, 3];
v.extend_from_within(..);   // double the contents

assert_eq!(v, [1, 2, 3, 1, 2, 3]);

It requires T: Clone, and clones each element exactly once. For Copy types like primitives it lowers to a single memcpy.

A real use: building patterns

It’s especially nice for building repeating or growing patterns where each step depends on the previous one — think DSP buffers, simple test fixtures, or doubling tricks:

1
2
3
4
5
6
7
let mut data = vec![1u32];
for _ in 0..4 {
    data.extend_from_within(..); // double in place
}

assert_eq!(data.len(), 16);
assert!(data.iter().all(|&x| x == 1));

When to reach for it

Any time you’d write v.extend_from_slice(&v[range]) and the borrow checker stops you, swap in v.extend_from_within(range). Stable since Rust 1.53, exists for both Vec<T> and VecDeque<T>, and quietly turns a frustrating compile error into a one-liner.

#103 Apr 2026

103. bool::then_some — Turn a Condition Into an Option Without an if

Writing if condition { Some(value) } else { None } for the hundredth time? bool::then_some collapses that whole pattern into a single chainable call.

The familiar boilerplate

You have a bool and you want to lift it into an Option. The naive form works, but it’s noisy and breaks chains:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn even_double(n: i32) -> Option<i32> {
    if n % 2 == 0 {
        Some(n * 2)
    } else {
        None
    }
}

assert_eq!(even_double(4), Some(8));
assert_eq!(even_double(5), None);

Three lines and an if/else for what is conceptually “if true, wrap it.” It also doesn’t slot into iterator chains — you’d have to wrap it in a closure.

then_some: the eager form

bool::then_some(value) returns Some(value) if the bool is true, None otherwise:

1
2
3
4
5
6
fn even_double(n: i32) -> Option<i32> {
    (n % 2 == 0).then_some(n * 2)
}

assert_eq!(even_double(4), Some(8));
assert_eq!(even_double(5), None);

One line, no branches in sight. The intent reads exactly like the English: if even, then some n * 2.

then: the lazy form

then_some always evaluates its argument — fine for cheap values, wasteful for expensive ones. Use then(|| ...) when the value should only be computed when the condition holds:

1
2
3
4
5
6
7
fn parse_if_digits(s: &str) -> Option<u64> {
    s.chars().all(|c| c.is_ascii_digit())
        .then(|| s.parse().unwrap())
}

assert_eq!(parse_if_digits("42"), Some(42));
assert_eq!(parse_if_digits("4a2"), None);

The parse only runs when the string is all digits. With then_some(s.parse().unwrap()) you’d unwrap on every call — instant panic on bad input.

Where it really shines: filter_map chains

The big payoff is in iterator pipelines. Filter and transform in one step, no closure-returning-Option boilerplate:

1
2
3
4
5
6
7
8
let nums = [1, 2, 3, 4, 5, 6];

let doubled_evens: Vec<i32> = nums
    .iter()
    .filter_map(|&n| (n % 2 == 0).then_some(n * 2))
    .collect();

assert_eq!(doubled_evens, [4, 8, 12]);

Compare to the if/else version inside the closure — it works, but it’s twice as wide and the else None arm adds zero information.

Watch the eager trap

then_some evaluates its argument unconditionally — which means an “obvious” guard like this is actually a panic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn first<T: Copy>(items: &[T]) -> Option<T> {
    // BAD: items[0] runs even when the slice is empty.
    // (!items.is_empty()).then_some(items[0])

    // GOOD: defer the indexing into the closure.
    (!items.is_empty()).then(|| items[0])
}

assert_eq!(first(&[10, 20, 30]), Some(10));
assert_eq!(first::<i32>(&[]), None);

The bool short-circuits, the closure doesn’t. Whenever the value can panic, unwrap, or just costs real work, reach for then, not then_some.

When to reach for which

Use then_some(value) when the value is already computed or trivially cheap — a literal, a copy, a field access. Use then(|| value) whenever building the value costs something or could panic. Both are stable (then since Rust 1.50, then_some since 1.62) and both read cleaner than the if/else they replace.

#104 Apr 2026

104. Vec::swap_remove — O(1) Removal When Order Doesn't Matter

Calling Vec::remove(i) shifts every element after i left by one — that’s O(n) per call. If you don’t care about order, swap_remove does the same job in O(1).

The hidden cost of Vec::remove

remove takes the value out of the vector, but to keep the rest in order it has to shift every following element left by one slot. On a long vector, removing near the front is a giant memcpy:

1
2
3
4
5
let mut v = vec![1, 2, 3, 4, 5];
let removed = v.remove(1);

assert_eq!(removed, 2);
assert_eq!(v, [1, 3, 4, 5]);

Inside a loop — “remove every item that matches X” — that O(n) cost compounds into O(n²). A tight loop that should be linear silently becomes quadratic.

swap_remove: trade order for speed

swap_remove(i) removes the element at index i by swapping it with the last element, then popping the tail. No shifting, no memcpy chain — just one swap and a pop:

1
2
3
4
5
6
let mut v = vec![1, 2, 3, 4, 5];
let removed = v.swap_remove(1);

assert_eq!(removed, 2);
// 5 took 2's spot — order is gone, but the op was O(1).
assert_eq!(v, [1, 5, 3, 4]);

If your Vec is really a set — entities in a game world, pending jobs in a worker pool, items keyed by id elsewhere — order rarely matters. swap_remove is free.

Removing many items in place

The classic mistake: iterate forward calling remove. swap_remove lets you do it without the O(n²) blow-up. Walk by index and don’t bump the cursor when you remove:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let mut nums = vec![1, 2, 3, 4, 5, 6, 7, 8];

let mut i = 0;
while i < nums.len() {
    if nums[i] % 2 == 0 {
        nums.swap_remove(i);
        // Don't increment — a new element is now at position i.
    } else {
        i += 1;
    }
}

nums.sort(); // canonicalize for the assert
assert_eq!(nums, [1, 3, 5, 7]);

For bulk predicate-based filtering, Vec::retain stays cleaner (and is also O(n)). Reach for swap_remove when you have a specific index you want gone in O(1).

With structs

swap_remove returns the removed value, so you can hand it off to another collection on the way out:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#[derive(PartialEq, Debug)]
struct Job { id: u32 }

let mut queue = vec![
    Job { id: 1 },
    Job { id: 2 },
    Job { id: 3 },
    Job { id: 4 },
];

let pulled = queue.swap_remove(0);

assert_eq!(pulled, Job { id: 1 });
assert_eq!(queue.len(), 3);

When to reach for it

Use swap_remove whenever you’d write remove(i) and the surrounding code doesn’t depend on element order. Order-preserving removal still needs remove, but most “remove one item from this Vec” code is order-agnostic — and swap_remove turns an O(n) pothole into a one-line O(1) win. Stable since Rust 1.0, one of those quietly-essential APIs that’s easy to forget exists.

#102 Apr 2026

102. slice::partition_point — Binary Search That Just Returns the Index

Reaching for binary_search on a sorted Vec and unwrapping Ok(i) | Err(i) because you only ever wanted the index? slice::partition_point skips the Result ceremony and hands you the position directly.

The binary_search annoyance

binary_search is great when you care whether the value was actually found. But often you don’t — you just want the spot where it would go to keep the slice sorted:

1
2
3
4
5
6
7
8
9
let nums = vec![1, 3, 5, 7, 9, 11];
let target = 6;

// Awkward: collapse Ok and Err to a single index.
let pos = match nums.binary_search(&target) {
    Ok(i) | Err(i) => i,
};

assert_eq!(pos, 3);

The Ok | Err pattern works, but it’s noisy and obscures the intent. Worse, it doesn’t generalise — what if you want the insertion point for a predicate, not an exact value?

partition_point to the rescue

partition_point takes a predicate and returns the first index where the predicate flips from true to false. On a sorted slice, that’s the insertion point — no Result, no match arms:

1
2
3
4
5
let nums = vec![1, 3, 5, 7, 9, 11];

let pos = nums.partition_point(|&x| x < 6);

assert_eq!(pos, 3); // 6 would slot between 5 and 7

The slice still has to be partitioned (all trues before all falses), but for a sorted slice with a < predicate that’s automatic. Internally it’s still O(log n) binary search — same complexity as binary_search, friendlier API.

Insert while keeping sorted

A common use: keep a Vec sorted as you add to it.

1
2
3
4
5
6
7
let mut leaderboard = vec![10, 25, 40, 70];
let new_score = 33;

let pos = leaderboard.partition_point(|&x| x < new_score);
leaderboard.insert(pos, new_score);

assert_eq!(leaderboard, [10, 25, 33, 40, 70]);

Compare that to binary_search(&new_score).unwrap_or_else(|i| i) — same result, more ceremony.

Beyond simple ordering

Because it takes any predicate, partition_point works on any slice partitioned by a property — not just sorted-by-Ord. Sorted by a derived key? Filter by a threshold? Same call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct Event { day: u32, name: &'static str }

let log = vec![
    Event { day: 1, name: "boot"   },
    Event { day: 2, name: "login"  },
    Event { day: 5, name: "deploy" },
    Event { day: 7, name: "alert"  },
    Event { day: 9, name: "reboot" },
];

// First event on or after day 5.
let i = log.partition_point(|e| e.day < 5);
assert_eq!(log[i].name, "deploy");

// Number of events strictly before day 5.
assert_eq!(log.partition_point(|e| e.day < 5), 2);

That second line is a slick trick: partition_point doubles as “count how many elements satisfy the prefix predicate” in O(log n).

When to reach for it

Any time you find yourself writing binary_search(...).unwrap_or_else(|i| i) or match ... { Ok(i) | Err(i) => i }, swap in partition_point. Stable since Rust 1.52 — old enough to use everywhere, fresh enough that plenty of code still does it the noisy way.

#101 Apr 2026

101. f64::total_cmp — Sort Floats Without the NaN Panic

Tried v.sort() on a Vec<f64> and hit the trait Ord is not implemented for f64? Then reached for .sort_by(|a, b| a.partial_cmp(b).unwrap()) and now a stray NaN is about to panic your service at 3am? f64::total_cmp is the one-liner that makes both problems disappear.

Why f64 doesn’t implement Ord

Floats form a partial order because NaN is not equal to anything — not even itself. So f64: PartialOrd but not Ord, which means sort() flat out refuses to compile:

1
2
let mut temps: Vec<f64> = vec![21.5, 18.0, 23.0, 19.5];
// temps.sort(); // ❌ the trait `Ord` is not implemented for `f64`

The classic workaround is partial_cmp().unwrap():

1
2
3
4
let mut temps: Vec<f64> = vec![21.5, 18.0, 23.0, 19.5];
temps.sort_by(|a, b| a.partial_cmp(b).unwrap());

assert_eq!(temps, [18.0, 19.5, 21.5, 23.0]);

Works — until a NaN sneaks in. Then partial_cmp returns None, the unwrap fires, and your sort becomes a panic.

Enter total_cmp

f64::total_cmp implements the IEEE 754 totalOrder predicate: a real total ordering on every f64 bit pattern, including all the NaNs. It returns Ordering directly — no Option, no panic:

1
2
3
4
let mut temps: Vec<f64> = vec![21.5, 18.0, 23.0, 19.5];
temps.sort_by(f64::total_cmp);

assert_eq!(temps, [18.0, 19.5, 21.5, 23.0]);

Same result for well-behaved input, but now NaN won’t take the process down:

1
2
3
4
5
6
7
8
9
let mut values: Vec<f64> = vec![3.0, f64::NAN, 1.0, f64::NEG_INFINITY, 2.0];
values.sort_by(f64::total_cmp);

// Finite values in order, -∞ at the front, NaN at the back.
assert_eq!(values[0], f64::NEG_INFINITY);
assert_eq!(values[1], 1.0);
assert_eq!(values[2], 2.0);
assert_eq!(values[3], 3.0);
assert!(values[4].is_nan());

min and max too

partial_cmp poisons more than just sort. Any time you reach for iter().max_by(|a, b| a.partial_cmp(b).unwrap()), you’ve written the same latent panic. total_cmp fits there too:

1
2
3
4
5
6
7
let readings = [3.2_f64, 1.4, 4.8, 2.1];

let peak = readings.iter().copied().max_by(f64::total_cmp).unwrap();
let low  = readings.iter().copied().min_by(f64::total_cmp).unwrap();

assert_eq!(peak, 4.8);
assert_eq!(low, 1.4);

Sorting structs by a float field

Because total_cmp takes two &f64s and returns Ordering, it slots straight into sort_by:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct Trade { symbol: &'static str, price: f64 }

let mut book = vec![
    Trade { symbol: "AAPL", price: 172.40 },
    Trade { symbol: "NVDA", price: 915.10 },
    Trade { symbol: "MSFT", price: 419.80 },
];

book.sort_by(|a, b| a.price.total_cmp(&b.price));

assert_eq!(book[0].symbol, "AAPL");
assert_eq!(book[2].symbol, "NVDA");

When to reach for it

Any time you’re about to type partial_cmp(...).unwrap() for a float, stop and use total_cmp instead. f32::total_cmp works the same way. Available since Rust 1.62 — the fix has been hiding in plain sight for years.

#100 Apr 2026

100. std::cmp::Reverse — Sort Descending Without Writing a Closure

Want to sort a Vec in descending order? The old trick is v.sort_by(|a, b| b.cmp(a)), and every time you write it you flip a and b and pray you got it right. std::cmp::Reverse is a one-word replacement.

The closure swap trick

To sort descending, you reverse the comparison. The closure is short but easy to mess up:

1
2
3
4
let mut scores = vec![30, 10, 50, 20, 40];
scores.sort_by(|a, b| b.cmp(a));

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

Swap the a and b and you silently get ascending order again. Not a bug the compiler can help with.

Enter Reverse

std::cmp::Reverse<T> is a tuple wrapper whose Ord impl is the reverse of T’s. Hand it to sort_by_key and you’re done:

1
2
3
4
5
6
use std::cmp::Reverse;

let mut scores = vec![30, 10, 50, 20, 40];
scores.sort_by_key(|&s| Reverse(s));

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

No chance of flipping the comparison the wrong way — the intent is right there in the name.

Sorting by a field, descending

It really shines when you’re sorting structs by a specific field:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use std::cmp::Reverse;

#[derive(Debug)]
struct Player {
    name: &'static str,
    score: u32,
}

let mut roster = vec![
    Player { name: "Ferris", score: 42 },
    Player { name: "Corro",  score: 88 },
    Player { name: "Rusty",  score: 65 },
];

roster.sort_by_key(|p| Reverse(p.score));

assert_eq!(roster[0].name, "Corro");
assert_eq!(roster[1].name, "Rusty");
assert_eq!(roster[2].name, "Ferris");

Mixing ascending and descending

Sort by one field ascending and another descending? Pack them in a tuple — Reverse composes cleanly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use std::cmp::Reverse;

let mut items = vec![
    ("apple",  3),
    ("banana", 1),
    ("apple",  1),
    ("banana", 3),
];

// Name ascending, then count descending.
items.sort_by_key(|&(name, count)| (name, Reverse(count)));

assert_eq!(items, [
    ("apple",  3),
    ("apple",  1),
    ("banana", 3),
    ("banana", 1),
]);

It works anywhere Ord does

Because Reverse<T> is an Ord type, you can use it with BinaryHeap to turn a max-heap into a min-heap, or with BTreeSet to iterate in reverse order — no extra wrapper types needed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::cmp::Reverse;
use std::collections::BinaryHeap;

// BinaryHeap is max-heap by default. Wrap in Reverse for min-heap behaviour.
let mut heap = BinaryHeap::new();
heap.push(Reverse(3));
heap.push(Reverse(1));
heap.push(Reverse(2));

assert_eq!(heap.pop(), Some(Reverse(1)));
assert_eq!(heap.pop(), Some(Reverse(2)));
assert_eq!(heap.pop(), Some(Reverse(3)));

When to reach for it

Any time you’d write b.cmp(a) — reach for Reverse instead. The code reads top-to-bottom, the intent is obvious, and there’s no comparator to accidentally flip.

98. sort_by_cached_key — Stop Recomputing Expensive Sort Keys

sort_by_key sounds like it computes the key once per element. It doesn’t — it calls your closure at every comparison, so an n-element sort can pay for that key O(n log n) times. If the key is expensive, sort_by_cached_key is the fix you’ve been looking for.

The trap

The signature reads nicely: “sort by this key.” The implementation, less so — the closure fires on every comparison, not once per element:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::cell::Cell;

let mut items = vec!["banana", "fig", "apple", "cherry", "date"];
let calls = Cell::new(0);

items.sort_by_key(|s| {
    calls.set(calls.get() + 1);
    // Pretend this is a heavy computation: allocating, hashing,
    // parsing, calling a regex, opening a file, etc.
    s.to_string()
});

// 5 elements, but the key ran way more than 5 times.
assert!(calls.get() > items.len());
assert_eq!(items, ["apple", "banana", "cherry", "date", "fig"]);

For identity keys that cost a pointer-deref, nobody cares. For anything that allocates.to_string(), .to_lowercase(), format!(...), a regex capture, a trimmed-and-lowered filename — the cost compounds quickly. I’ve seen a profile where 80% of total runtime was the key closure being called 40,000 times to sort 2,000 items.

The fix

slice::sort_by_cached_key runs your closure exactly once per element, stashes the results in a scratch buffer, then sorts against the cache. This is the Schwartzian transform, wrapped up in a method call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::cell::Cell;

let mut items = vec!["banana", "fig", "apple", "cherry", "date"];
let calls = Cell::new(0);

items.sort_by_cached_key(|s| {
    calls.set(calls.get() + 1);
    s.to_string()
});

// Exactly one call per element — no matter how big the slice is.
assert_eq!(calls.get(), items.len());
assert_eq!(items, ["apple", "banana", "cherry", "date", "fig"]);

Same result, linear key-function calls. The memory trade is a Vec<(K, usize)> the size of the slice — cheap next to the cost of re-running an allocating closure on every compare.

When to reach for which

The rule is about where your time goes, not how fancy the key looks:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let mut nums = vec![5u32, 2, 8, 1, 9, 3];

// Trivial key: sort_by_key is fine (and avoids the scratch alloc).
nums.sort_by_key(|n| *n);
assert_eq!(nums, [1, 2, 3, 5, 8, 9]);

// Expensive key: sort_by_cached_key wins.
let mut files = vec!["Cargo.TOML", "src/MAIN.rs", "README.md", "build.RS"];
files.sort_by_cached_key(|path| path.to_lowercase());
assert_eq!(files, ["build.RS", "Cargo.TOML", "README.md", "src/MAIN.rs"]);

Use sort_by_key for cheap, Copy-ish keys. Use sort_by_cached_key the moment your closure allocates, hashes, parses, or otherwise does real work — it’s the difference between O(n log n) and O(n) calls to that closure.

#097 Apr 2026

97. Option::take_if — Take the Value Out Only When You Want To

You want to pull a value out of an Option — but only if it meets some condition. Before take_if, that little sentence turned into a five-line dance with as_ref, is_some_and, and a separate take(). Now it’s one call.

The old dance

Say you hold an Option<Session> and you want to evict it if it’s expired, otherwise leave it alone. The naive version keeps ownership juggling in your face:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#[derive(Debug, PartialEq)]
struct Session { id: u32, age: u32 }

let mut slot: Option<Session> = Some(Session { id: 1, age: 45 });

// Old way: peek, decide, then take.
let evicted = if slot.as_ref().is_some_and(|s| s.age > 30) {
    slot.take()
} else {
    None
};

assert_eq!(evicted, Some(Session { id: 1, age: 45 }));
assert!(slot.is_none());

Three lines of control flow just to express “take it if it’s stale.” And if you ever need to inspect the value more deeply, you’re one borrow-checker nudge away from rewriting the whole thing.

Enter take_if

Option::take_if bakes the whole pattern into one method — it runs your predicate on the inner value, and if the predicate returns true, it take()s it for you:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let mut slot: Option<Session> = Some(Session { id: 2, age: 12 });

// Young session — predicate is false, nothing happens.
let evicted = slot.take_if(|s| s.age > 30);
assert_eq!(evicted, None);
assert!(slot.is_some());

// Age it and try again.
if let Some(s) = slot.as_mut() { s.age = 99; }
let evicted = slot.take_if(|s| s.age > 30);
assert_eq!(evicted.unwrap().id, 2);
assert!(slot.is_none());

One line, one branch, one name for the pattern.

The mutable twist

Here’s the detail that trips people up: the predicate receives &mut T, not &T. You can mutate the inner value before deciding whether to yank it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let mut counter: Option<u32> = Some(0);

// Bump on every call; take it once it hits the threshold.
for _ in 0..5 {
    let taken = counter.take_if(|n| {
        *n += 1;
        *n >= 3
    });
    if let Some(n) = taken {
        assert_eq!(n, 3);
    }
}

assert!(counter.is_none());

Useful for cache eviction with hit counters, retry-until-exhausted slots, or anywhere “inspect, maybe mutate, maybe remove” shows up. Reach for take_if whenever you find yourself writing if condition { x.take() } else { None }.

95. LazyLock::get — Peek at a Lazy Value Without Initializing It

Wanted to know whether a LazyLock has been initialized yet — without causing the initialization by touching it? Rust 1.94 stabilises LazyLock::get and LazyCell::get, which return Option<&T> and leave the closure untouched if it hasn’t fired.

The old pain

Any access that goes through Deref forces the closure to run. So the moment you do *CONFIG — or anything that implicitly derefs — the lazy becomes eager. Before 1.94 there was no way to ask “has this been initialized?” without tripping that wire.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::sync::LazyLock;

static CONFIG: LazyLock<Vec<String>> = LazyLock::new(|| {
    // Imagine this is slow: reads a file, hits the network, etc.
    vec!["debug".into(), "verbose".into()]
});

fn main() {
    // No way to peek without paying the init cost
    let _ = CONFIG.len(); // forces init
    assert_eq!(CONFIG.len(), 2);
}

Handy enough, but if you want a metric like “was the config ever read?” you’d have to wrap the whole thing in your own AtomicBool.

The fix: LazyLock::get

Called as an associated function (LazyLock::get(&lock)), it returns Option<&T> without touching the closure. None means the lazy is still pending. Some(&value) means someone already forced it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::sync::LazyLock;

static CONFIG: LazyLock<Vec<String>> = LazyLock::new(|| {
    vec!["debug".into(), "verbose".into()]
});

fn main() {
    // Not yet initialised — get returns None
    assert!(LazyLock::get(&CONFIG).is_none());

    // Force init via normal deref
    assert_eq!(CONFIG.len(), 2);

    // Now get returns Some(&value)
    let peek = LazyLock::get(&CONFIG).unwrap();
    assert_eq!(peek.len(), 2);
}

Note the call style: LazyLock::get(&CONFIG), not CONFIG.get(). That’s deliberate — method-lookup on the lock itself would go through Deref, which is exactly the thing we’re trying to avoid.

Same story for LazyCell

LazyCell is the single-threaded cousin and gets the same treatment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::cell::LazyCell;

fn main() {
    let greeting = LazyCell::new(|| "Hello, Rust!".to_uppercase());

    // Not forced yet
    assert!(LazyCell::get(&greeting).is_none());

    // Deref triggers the closure
    assert_eq!(*greeting, "HELLO, RUST!");

    // Now we can peek without re-running anything
    assert_eq!(LazyCell::get(&greeting), Some(&"HELLO, RUST!".to_string()));
}

Where this shines

Two patterns fall out naturally.

Metrics and diagnostics. You want to log “did we ever load the config?” at shutdown without accidentally loading it just to find out:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::sync::LazyLock;

static CACHE: LazyLock<Vec<u64>> = LazyLock::new(|| (0..1000).collect());

fn was_cache_used() -> bool {
    LazyLock::get(&CACHE).is_some()
}

fn main() {
    assert!(!was_cache_used());
    let _ = CACHE.first(); // touch it
    assert!(was_cache_used());
}

Tests. Assert that the lazy init didn’t happen on a code path that shouldn’t need it — a guarantee that was basically impossible to write before.

When to reach for it

Use LazyLock::get / LazyCell::get whenever you need to ask about initialization state without causing it. For everything else, just deref as usual — that’s still the one-liner that Just Works.

Stabilised in Rust 1.94 (March 2026).

92. core::range — Range Types You Can Actually Copy

Ever tried to reuse a 0..=10 range and hit “use of moved value”? Rust 1.95 stabilises core::range, a new module of range types that implement Copy.

The old pain

The classic range types in std::opsRange, RangeInclusive — are iterators themselves. That means iterating them consumes them, and they can’t be Copy:

1
2
3
4
let r = 0..=5;
let a: Vec<_> = r.clone().collect();
let b: Vec<_> = r.collect(); // consumes r
// r is gone here

Every time you want to reuse a range you reach for .clone() or rebuild it. Annoying for something that looks like a pair of integers.

The fix: core::range

Rust 1.95 adds new range types in core::range (re-exported as std::range). They’re plain data — Copy, no iterator state baked in — and you turn them into an iterator on demand with .into_iter():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use core::range::RangeInclusive;

fn main() {
    let r: RangeInclusive<i32> = (0..=5).into();

    let a: Vec<i32> = r.into_iter().collect();
    let b: Vec<i32> = r.into_iter().collect(); // r is Copy, still usable

    assert_eq!(a, vec![0, 1, 2, 3, 4, 5]);
    assert_eq!(a, b);
}

No .clone(), no rebuild. r is just two numbers behind the scenes, so copying it is free.

Passing ranges around

Because the new types are Copy, you can pass them into helpers without worrying about move semantics:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use core::range::RangeInclusive;

fn sum_range(r: RangeInclusive<i32>) -> i32 {
    r.into_iter().sum()
}

fn main() {
    let r: RangeInclusive<i32> = (1..=4).into();

    assert_eq!(sum_range(r), 10);
    assert_eq!(sum_range(r), 10); // reuse: r is Copy
}

The old std::ops::RangeInclusive would have been moved by the first call.

Field access, not method calls

The new types expose their bounds as public fields — no more .start() / .end() accessors. For RangeInclusive, the upper bound is called last (emphasising that it’s included):

1
2
3
4
5
6
7
8
use core::range::RangeInclusive;

fn main() {
    let r: RangeInclusive<i32> = (10..=20).into();

    assert_eq!(r.start, 10);
    assert_eq!(r.last, 20);
}

Exclusive Range has start and end in the same way. Either one is simple plain-old-data that you can pattern-match, destructure, or read directly.

When to reach for it

Use core::range whenever you want to store, pass, or reuse a range as a value. The old std::ops ranges are still everywhere (literal syntax, slice indexing, for loops), so there’s no rush to migrate — but for library APIs that take ranges as parameters, the new types are the friendlier choice.

Stabilised in Rust 1.95 (April 2026).

90. black_box — Stop the Compiler From Erasing Your Benchmarks

Your benchmark ran in 0 nanoseconds? Congratulations — the compiler optimised away the code you were trying to measure. std::hint::black_box prevents that by hiding values from the optimiser.

The problem: the optimiser is too smart

The Rust compiler aggressively eliminates dead code. If it can prove a result is never used, it simply removes the computation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn sum_range(n: u64) -> u64 {
    (0..n).sum()
}

fn main() {
    let start = std::time::Instant::now();
    let result = sum_range(10_000_000);
    let elapsed = start.elapsed();

    // Without using `result`, the compiler may skip the entire computation
    println!("took: {elapsed:?}");

    // Force the result to be "used" so the above isn't optimised out
    assert!(result > 0);
}

In release mode, the compiler can see through this and may still optimise the loop away — or even compute the answer at compile time. Your benchmark reports near-zero time, and you learn nothing.

Enter black_box

std::hint::black_box takes a value and returns it unchanged, but the compiler treats it as an opaque barrier — it can’t see through it, so it can’t optimise away whatever produced that value:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::hint::black_box;

fn sum_range(n: u64) -> u64 {
    (0..n).sum()
}

fn main() {
    let start = std::time::Instant::now();
    let result = sum_range(black_box(10_000_000));
    let _ = black_box(result);
    let elapsed = start.elapsed();

    println!("sum = {result}, took: {elapsed:?}");
}

Two black_box calls do the trick:

  1. Wrap the input — prevents the compiler from constant-folding the argument
  2. Wrap the output — prevents dead-code elimination of the computation

Before and after

Without black_box (release mode):

1
sum = 49999995000000, took: 83ns     ← suspiciously fast

With black_box (release mode):

1
sum = 49999995000000, took: 5.612ms  ← actual work

It works on any type

black_box is generic — it works on integers, strings, structs, references, whatever:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::hint::black_box;

fn main() {
    // Hide a vector from the optimiser
    let data: Vec<i32> = black_box(vec![1, 2, 3, 4, 5]);
    let total: i32 = data.iter().sum();
    let total = black_box(total);

    assert_eq!(total, 15);
}

Micro-benchmark recipe

Here’s a minimal pattern for quick-and-dirty micro-benchmarks:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use std::hint::black_box;
use std::time::Instant;

fn fibonacci(n: u32) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

fn main() {
    let iterations = 100;
    let start = Instant::now();
    for _ in 0..iterations {
        black_box(fibonacci(black_box(30)));
    }
    let elapsed = start.elapsed();
    println!("{iterations} runs in {elapsed:?} ({:?}/iter)", elapsed / iterations);
}

Without black_box, the compiler could hoist the pure function call out of the loop or eliminate it entirely. With it, each iteration does real work.

When to use it

Reach for black_box whenever you’re timing code and the results look suspiciously fast. It’s also the foundation that benchmarking frameworks like criterion and the built-in #[bench] use under the hood.

It’s not a full benchmarking harness — for serious measurement you still want warmup, statistics, and outlier detection. But when you need a quick sanity check, black_box + Instant gets the job done.

Available since Rust 1.66 on stable.

89. cold_path — Tell the Compiler Which Branch Won't Happen

Your error-handling branch fires once in a million calls, but the compiler doesn’t know that. core::hint::cold_path lets you mark unlikely branches so the optimiser can focus on the hot path.

Why branch layout matters

Modern CPUs predict which way a branch will go and speculatively execute instructions ahead of time. When the prediction is right, execution flies. When it’s wrong, the pipeline stalls.

Compilers already try to guess which branches are hot, but they don’t always get it right — especially when both sides of an if look equally plausible from a static analysis perspective. That’s where cold_path comes in.

The basics

Call cold_path() at the start of a branch that is rarely taken:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::hint::cold_path;

fn process(value: Option<u64>) -> u64 {
    if let Some(v) = value {
        v * 2
    } else {
        cold_path();
        log_miss();
        0
    }
}

fn log_miss() {
    // imagine logging or metrics here
}

The compiler now knows the else arm is unlikely. It can lay out the machine code so the hot path (the Some arm) has no jumps, keeping it in the instruction cache and the branch predictor happy.

In match expressions

cold_path works well in match arms too — mark the rare variants:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::hint::cold_path;

fn handle_status(code: u16) -> &'static str {
    match code {
        200 => "ok",
        301 => "moved",
        404 => { cold_path(); "not found" }
        500 => { cold_path(); "server error" }
        _   => { cold_path(); "unknown" }
    }
}

Only the branches you expect to be common stay on the fast track.

Building likely and unlikely helpers

If you’ve used C/C++, you might miss __builtin_expect. With cold_path you can build the same thing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use std::hint::cold_path;

#[inline(always)]
const fn likely(b: bool) -> bool {
    if !b { cold_path(); }
    b
}

#[inline(always)]
const fn unlikely(b: bool) -> bool {
    if b { cold_path(); }
    b
}

fn check_bounds(index: usize, len: usize) -> bool {
    if unlikely(index >= len) {
        panic!("out of bounds: {} >= {}", index, len);
    }
    true
}

Now you can annotate conditions directly instead of marking individual branches.

A word of caution

Misusing cold_path on a branch that actually runs often can hurt performance — the compiler will deprioritise it, and you’ll get more pipeline stalls, not fewer. Always benchmark before sprinkling hints around. Profile first, hint second.

The bottom line

cold_path is a zero-cost, zero-argument function that tells the optimiser what you already know: this branch is the exception, not the rule. It’s a small tool, but in hot loops and latency-sensitive code, it can make a measurable difference.

Stabilised in Rust 1.95 (April 2026).

88. Vec::push_mut — Push and Modify in One Step

Tired of pushing a default into a Vec and then grabbing a mutable reference to fill it in? Rust 1.95 stabilises push_mut, which returns &mut T to the element it just inserted.

The old dance

A common pattern is to push a placeholder value and then immediately mutate it. Before push_mut, you had to do an awkward two-step:

1
2
3
4
5
6
7
8
9
let mut names = Vec::new();

// Push, then index back in to mutate
names.push(String::new());
let last = names.last_mut().unwrap();
last.push_str("hello");
last.push_str(", world");

assert_eq!(names[0], "hello, world");

That last_mut().unwrap() is boilerplate — you know the element is there because you just pushed it. Worse, in more complex code the compiler can’t always prove the reference is safe, forcing you into index gymnastics.

Enter push_mut

push_mut pushes the value and hands you back a mutable reference in one shot:

1
2
3
4
5
6
7
let mut scores = Vec::new();

let entry = scores.push_mut(0);
*entry += 10;
*entry += 25;

assert_eq!(scores[0], 35);

No unwraps, no indexing, no second lookup. The borrow checker is happy because there’s a clear chain of ownership.

Building structs in-place

This really shines when you’re constructing complex values piece by piece:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#[derive(Debug, Default)]
struct Player {
    name: String,
    score: u32,
    active: bool,
}

let mut roster = Vec::new();

let p = roster.push_mut(Player::default());
p.name = String::from("Ferris");
p.score = 100;
p.active = true;

assert_eq!(roster[0].name, "Ferris");
assert_eq!(roster[0].score, 100);
assert!(roster[0].active);

Instead of building the struct fully before pushing, you push a default and fill it in. This can be handy when the final field values depend on context you only have after insertion.

Also works for insert_mut

Need to insert at a specific index? insert_mut follows the same pattern:

1
2
3
4
5
let mut v = vec![1, 3, 4];
let inserted = v.insert_mut(1, 2);
*inserted *= 10;

assert_eq!(v, [1, 20, 3, 4]);

Both methods are also available on VecDeque (push_front_mut, push_back_mut, insert_mut) and LinkedList (push_front_mut, push_back_mut).

When to reach for it

Use push_mut whenever you’d otherwise write push followed by last_mut().unwrap(). It’s one less unwrap, one fewer line, and clearer intent: push this, then let me tweak it.

Stabilised in Rust 1.95 (April 2026) — update your toolchain and give it a spin.

#087 Apr 2026

87. Atomic update — Kill the Compare-and-Swap Loop

Every Rust developer who’s written lock-free code has written the same compare_exchange loop. Rust 1.95 finally gives atomics an update method that does it for you.

The old way

Atomically doubling a counter used to mean writing a retry loop yourself:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::sync::atomic::{AtomicUsize, Ordering};

let counter = AtomicUsize::new(10);

loop {
    let current = counter.load(Ordering::Relaxed);
    let new_val = current * 2;
    match counter.compare_exchange(
        current, new_val,
        Ordering::SeqCst, Ordering::Relaxed,
    ) {
        Ok(_) => break,
        Err(_) => continue,
    }
}
// counter is now 20

It works, but it’s boilerplate — and easy to get wrong (use the wrong ordering, forget to retry, etc.).

The new way: update

1
2
3
4
5
6
use std::sync::atomic::{AtomicUsize, Ordering};

let counter = AtomicUsize::new(10);

counter.update(Ordering::SeqCst, Ordering::SeqCst, |x| x * 2);
// counter is now 20

One line. No loop. No chance of forgetting to retry on contention.

The method takes two orderings (one for the store on success, one for the load on failure) and a closure that transforms the current value. It handles the compare-and-swap retry loop internally.

It returns the previous value

Just like fetch_add and friends, update returns the value before the update:

1
2
3
4
5
6
7
use std::sync::atomic::{AtomicUsize, Ordering};

let counter = AtomicUsize::new(5);

let prev = counter.update(Ordering::SeqCst, Ordering::SeqCst, |x| x + 3);
assert_eq!(prev, 5);  // was 5
assert_eq!(counter.load(Ordering::SeqCst), 8);  // now 8

This makes it perfect for “fetch-and-modify” patterns where you need the old value.

Works on all atomic types

update isn’t just for AtomicUsize — it’s available on AtomicBool, AtomicIsize, AtomicUsize, and AtomicPtr too:

1
2
3
4
5
use std::sync::atomic::{AtomicBool, Ordering};

let flag = AtomicBool::new(false);
flag.update(Ordering::SeqCst, Ordering::SeqCst, |x| !x);
assert_eq!(flag.load(Ordering::SeqCst), true);

When to use update vs fetch_add

If your operation is a simple add, sub, or bitwise op, the specialized fetch_* methods are still better — they compile down to a single atomic instruction on most architectures.

Use update when your transformation is more complex: clamping, toggling state machines, applying arbitrary functions. Anywhere you’d previously hand-roll a CAS loop.

Summary

MethodUse when
fetch_add, fetch_or, etc.Simple arithmetic/bitwise ops
updateArbitrary transformations (Rust 1.95+)
Manual CAS loopNever again (mostly)

Available on stable since Rust 1.95.0 for AtomicBool, AtomicIsize, AtomicUsize, and AtomicPtr.

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.

#080 Apr 2026

80. VecDeque::pop_front_if and pop_back_if — Conditional Pops on Both Ends

Vec::pop_if got a deque-shaped sibling. As of Rust 1.93, VecDeque has pop_front_if and pop_back_if — conditional pops on either end without the peek-then-pop dance.

The problem

You want to remove an element from a VecDeque only when it matches a predicate. Before 1.93, you’d peek, match, then pop:

1
2
3
4
5
6
7
use std::collections::VecDeque;

let mut queue: VecDeque<i32> = VecDeque::from([1, 2, 3, 4]);

if queue.front().is_some_and(|&x| x == 1) {
    queue.pop_front();
}

Two lookups, two branches, one opportunity to desynchronize the check from the pop if you refactor the predicate later.

The fix

pop_front_if takes a closure, checks the front element against it, and pops it only if it matches. pop_back_if does the same on the other end.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::collections::VecDeque;

let mut queue: VecDeque<i32> = VecDeque::from([1, 2, 3, 4]);

let popped = queue.pop_front_if(|x| *x == 1);
assert_eq!(popped, Some(1));
assert_eq!(queue, VecDeque::from([2, 3, 4]));

// Predicate doesn't match — nothing is popped.
let not_popped = queue.pop_front_if(|x| *x > 100);
assert_eq!(not_popped, None);
assert_eq!(queue, VecDeque::from([2, 3, 4]));

The return type is Option<T>: Some(value) if the predicate matched and the element was removed, None otherwise (including when the deque is empty).

One subtle detail worth noting — the closure receives &mut T, not &T. That means |&x| won’t type-check; use |x| *x == ... or destructure with |&mut x|. The extra flexibility lets you mutate the element in-place before deciding to pop it.

Draining a prefix

The pattern clicks when you pair it with a while let loop. Drain everything at the front that matches a condition, stop the moment it doesn’t:

1
2
3
4
5
6
7
8
use std::collections::VecDeque;

let mut events: VecDeque<i32> = VecDeque::from([1, 2, 3, 10, 11, 12]);

// Drain the "small" prefix only.
while let Some(_) = events.pop_front_if(|x| *x < 10) {}

assert_eq!(events, VecDeque::from([10, 11, 12]));

No index tracking, no split_off, no collecting into a new deque.

Why both ends?

VecDeque is a double-ended ring buffer, so it’s natural to support the same idiom on both sides. Processing a priority queue from the back, trimming expired entries from the front, popping a sentinel only when it’s still there — all one method call each.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::collections::VecDeque;

let mut log: VecDeque<&str> = VecDeque::from(["START", "a", "b", "c", "END"]);

let end = log.pop_back_if(|s| *s == "END");
let start = log.pop_front_if(|s| *s == "START");

assert_eq!(start, Some("START"));
assert_eq!(end, Some("END"));
assert_eq!(log, VecDeque::from(["a", "b", "c"]));

When to reach for it

Whenever the shape of your code is “peek, compare, pop.” That’s the tell. pop_front_if / pop_back_if collapse three steps into one atomic operation, and the Option<T> return makes it composable with while let, ?, and the rest of the Option toolbox.

Stabilized in Rust 1.93 — if your MSRV is recent enough, this is a free readability win.

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

#077 Apr 2026

77. repeat_n — Repeat a Value Exactly N Times

Stop writing repeat(x).take(n) — there’s a dedicated function that’s both cleaner and more efficient.

The old way

If you wanted an iterator that yields a value a fixed number of times, you’d chain repeat with take:

1
2
3
4
use std::iter;

let greetings: Vec<_> = iter::repeat("hello").take(3).collect();
assert_eq!(greetings, vec!["hello", "hello", "hello"]);

This works fine for Copy types, but it always clones the value — even on the last iteration, where you could just move it instead.

Enter repeat_n

std::iter::repeat_n does exactly what the name says — repeats a value n times:

1
2
3
4
use std::iter;

let greetings: Vec<_> = iter::repeat_n("hello", 3).collect();
assert_eq!(greetings, vec!["hello", "hello", "hello"]);

Cleaner, more readable, and it comes with a hidden superpower.

The efficiency win

repeat_n moves the value on the last iteration instead of cloning it. This matters when cloning is expensive:

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

let original = vec![1, 2, 3]; // expensive to clone
let copies: Vec<Vec<i32>> = iter::repeat_n(original, 3).collect();

assert_eq!(copies.len(), 3);
assert_eq!(copies[0], vec![1, 2, 3]);
assert_eq!(copies[1], vec![1, 2, 3]);
assert_eq!(copies[2], vec![1, 2, 3]);
// The first two are clones, but the third one is a move — one fewer allocation!

With repeat(x).take(n), you’d clone all n times. With repeat_n, you save one clone. For large buffers or complex types, that’s a meaningful win.

repeat_n with zero

Passing n = 0 yields an empty iterator, and the value is simply dropped — no clones happen at all:

1
2
3
4
use std::iter;

let items: Vec<String> = iter::repeat_n(String::from("unused"), 0).collect();
assert!(items.is_empty());

When to reach for it

Use repeat_n whenever you need a fixed number of identical values. Common patterns:

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

// Initialize a grid row
let row: Vec<f64> = iter::repeat_n(0.0, 10).collect();
assert_eq!(row.len(), 10);

// Pad a sequence
let mut data = vec![1, 2, 3];
data.extend(iter::repeat_n(0, 5));
assert_eq!(data, vec![1, 2, 3, 0, 0, 0, 0, 0]);

Small change, but it makes intent crystal clear: you want exactly n copies, no more, no less.

#068 Apr 2026

68. f64::next_up — Walk the Floating Point Number Line

Ever wondered what the next representable floating point number after 1.0 is? Since Rust 1.86, f64::next_up and f64::next_down let you step through the number line one float at a time.

The problem

Floating point numbers aren’t evenly spaced — the gap between representable values grows as the magnitude increases. Before next_up / next_down, figuring out the next neighbor required bit-level manipulation of the IEEE 754 representation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    // The hard way (before 1.86): manually decode bits
    let x: f64 = 1.0;
    let bits = x.to_bits();
    let next_bits = bits + 1;
    let next = f64::from_bits(next_bits);

    assert!(next > x);
    assert_eq!(next, 1.0000000000000002);
}

Error-prone, unreadable, and doesn’t handle edge cases like negative numbers, zero, or special values.

The clean way

next_up returns the smallest f64 greater than self. next_down returns the largest f64 less than self:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    let x: f64 = 1.0;

    let up = x.next_up();
    let down = x.next_down();

    assert!(up > x);
    assert!(down < x);
    assert_eq!(up, 1.0000000000000002);
    assert_eq!(down, 0.9999999999999998);

    // There's no float between x and its neighbors
    assert_eq!(up.next_down(), x);
    assert_eq!(down.next_up(), x);
}

They handle all the edge cases you’d rather not think about — negative numbers, subnormals, and infinity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    // Works across zero
    assert_eq!(0.0_f64.next_up(), 5e-324);   // smallest positive f64
    assert_eq!(0.0_f64.next_down(), -5e-324); // largest negative f64

    // Infinity is the boundary
    assert_eq!(f64::MAX.next_up(), f64::INFINITY);
    assert_eq!(f64::INFINITY.next_up(), f64::INFINITY);

    // NaN stays NaN
    assert!(f64::NAN.next_up().is_nan());
}

Practical use: precision-aware comparisons

The gap between adjacent floats is called an ULP (unit in the last place). next_up lets you build tolerance-aware comparisons without guessing at epsilon values:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn almost_equal(a: f64, b: f64, max_ulps: u32) -> bool {
    if a == b { return true; }

    let mut current = a;
    for _ in 0..max_ulps {
        current = if a < b { current.next_up() } else { current.next_down() };
        if current == b { return true; }
    }
    false
}

fn main() {
    let a = 0.1 + 0.2;
    let b = 0.3;

    // They're not equal...
    assert_ne!(a, b);

    // ...but they're within 1 ULP of each other
    assert!(almost_equal(a, b, 1));
}

Also available on f32 with the same API. These methods are const fn, so you can use them in const contexts too.

63. fmt::from_fn — Display Anything With a Closure

Need a quick Display impl without defining a whole new type? std::fmt::from_fn turns any closure into a formattable value — ad-hoc Display in one line.

The problem: Display needs a type

Normally, implementing Display means creating a wrapper struct just to control how something gets formatted:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use std::fmt;

struct CommaSeparated<'a>(&'a [i32]);

impl fmt::Display for CommaSeparated<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (i, val) in self.0.iter().enumerate() {
            if i > 0 { write!(f, ", ")?; }
            write!(f, "{val}")?;
        }
        Ok(())
    }
}

fn main() {
    let nums = vec![1, 2, 3];
    let display = CommaSeparated(&nums);
    assert_eq!(display.to_string(), "1, 2, 3");
}

That’s a lot of boilerplate for “join with commas.”

After: fmt::from_fn

Stabilized in Rust 1.93, std::fmt::from_fn takes a closure Fn(&mut Formatter) -> fmt::Result and returns a value that implements Display (and Debug):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::fmt;

fn main() {
    let nums = vec![1, 2, 3];

    let display = fmt::from_fn(|f| {
        for (i, val) in nums.iter().enumerate() {
            if i > 0 { write!(f, ", ")?; }
            write!(f, "{val}")?;
        }
        Ok(())
    });

    assert_eq!(display.to_string(), "1, 2, 3");
    // Works directly in format strings too
    assert_eq!(format!("numbers: {display}"), "numbers: 1, 2, 3");
}

No wrapper type, no trait impl — just a closure that writes to a formatter.

Building reusable formatters

Wrap from_fn in a function and you have a reusable formatter without the boilerplate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::fmt;

fn join_with<'a, I, T>(iter: I, sep: &'a str) -> impl fmt::Display + 'a
where
    I: IntoIterator<Item = T> + 'a,
    T: fmt::Display + 'a,
{
    fmt::from_fn(move |f| {
        let mut first = true;
        for item in iter {
            if !first { write!(f, "{sep}")?; }
            first = false;
            write!(f, "{item}")?;
        }
        Ok(())
    })
}

fn main() {
    let tags = vec!["rust", "formatting", "closures"];
    assert_eq!(join_with(tags, " | ").to_string(), "rust | formatting | closures");

    let nums = vec![10, 20, 30];
    assert_eq!(format!("sum of [{}]", join_with(nums, " + ")), "sum of [10 + 20 + 30]");
}

Lazy formatting — no allocation until needed

from_fn is lazy — the closure only runs when the value is actually formatted. This makes it perfect for logging where most messages might be filtered out:

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

fn debug_summary(data: &[u8]) -> impl fmt::Display + '_ {
    fmt::from_fn(move |f| {
        write!(f, "[{} bytes: ", data.len())?;
        for (i, byte) in data.iter().take(4).enumerate() {
            if i > 0 { write!(f, " ")?; }
            write!(f, "{byte:02x}")?;
        }
        if data.len() > 4 { write!(f, " ...")?; }
        write!(f, "]")
    })
}

fn main() {
    let payload = vec![0xDE, 0xAD, 0xBE, 0xEF, 0x42, 0x00];
    let summary = debug_summary(&payload);

    // The closure hasn't run yet — zero work done
    // It only formats when you actually use it:
    assert_eq!(summary.to_string(), "[6 bytes: de ad be ef ...]");
}

No String is allocated unless something actually calls Display::fmt. Compare that to eagerly building a debug string just in case.

from_fn also implements Debug

The returned value implements both Display and Debug, so it works with {:?} too:

1
2
3
4
5
6
7
use std::fmt;

fn main() {
    let val = fmt::from_fn(|f| write!(f, "custom"));
    assert_eq!(format!("{val}"),   "custom");
    assert_eq!(format!("{val:?}"), "custom");
}

fmt::from_fn is a tiny addition to std that removes a surprisingly common friction point — any time you’d reach for a newtype just to implement Display, try a closure instead.

#062 Apr 2026

62. Iterator::flat_map — Map and Flatten in One Step

Need to transform each element into multiple items and collect them all into a flat sequence? flat_map combines map and flatten into a single, expressive call.

The nested iterator problem

Say you have a list of sentences and want all individual words. The naive approach with map gives you an iterator of iterators:

1
2
3
4
5
6
7
8
let sentences = vec!["hello world", "foo bar baz"];

// map gives us an iterator of Split iterators — not what we want
let nested: Vec<Vec<&str>> = sentences.iter()
    .map(|s| s.split_whitespace().collect())
    .collect();

assert_eq!(nested, vec![vec!["hello", "world"], vec!["foo", "bar", "baz"]]);

You could chain .map().flatten(), but flat_map does both at once:

1
2
3
4
5
6
7
let sentences = vec!["hello world", "foo bar baz"];

let words: Vec<&str> = sentences.iter()
    .flat_map(|s| s.split_whitespace())
    .collect();

assert_eq!(words, vec!["hello", "world", "foo", "bar", "baz"]);

Expanding one-to-many relationships

flat_map shines when each input element maps to zero or more outputs. Think of it as a one-to-many transform:

1
2
3
4
5
6
7
8
let numbers = vec![1, 2, 3];

// Each number expands to itself and its double
let expanded: Vec<i32> = numbers.iter()
    .flat_map(|&n| vec![n, n * 2])
    .collect();

assert_eq!(expanded, vec![1, 2, 2, 4, 3, 6]);

Filtering and transforming at once

Since flat_map’s closure can return an empty iterator, it naturally combines filtering and mapping — just return None or Some:

1
2
3
4
5
6
7
let inputs = vec!["42", "not_a_number", "7", "oops", "13"];

let parsed: Vec<i32> = inputs.iter()
    .flat_map(|s| s.parse::<i32>().ok())
    .collect();

assert_eq!(parsed, vec![42, 7, 13]);

This works because Option implements IntoIteratorSome(x) yields one item, None yields zero. It’s equivalent to filter_map, but flat_map generalizes to any iterator, not just Option.

Traversing nested structures

Got a tree-like structure? flat_map lets you drill into children naturally:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct Team {
    name: &'static str,
    members: Vec<&'static str>,
}

let teams = vec![
    Team { name: "backend", members: vec!["Alice", "Bob"] },
    Team { name: "frontend", members: vec!["Carol"] },
    Team { name: "devops", members: vec!["Dave", "Eve", "Frank"] },
];

let all_members: Vec<&str> = teams.iter()
    .flat_map(|team| team.members.iter().copied())
    .collect();

assert_eq!(all_members, vec!["Alice", "Bob", "Carol", "Dave", "Eve", "Frank"]);

flat_map vs map + flatten

They’re semantically identical — flat_map(f) is just map(f).flatten(). But flat_map reads better and signals your intent: “each element produces multiple items, and I want them all in one sequence.”

1
2
3
4
5
6
7
8
let data = vec![vec![1, 2], vec![3], vec![4, 5, 6]];

// These are equivalent:
let a: Vec<i32> = data.iter().flat_map(|v| v.iter().copied()).collect();
let b: Vec<i32> = data.iter().map(|v| v.iter().copied()).flatten().collect();

assert_eq!(a, b);
assert_eq!(a, vec![1, 2, 3, 4, 5, 6]);

flat_map has been stable since Rust 1.0 — it’s a fundamental iterator combinator that replaces nested loops with clean, composable pipelines.

#061 Apr 2026

61. Iterator::reduce — Fold Without an Initial Value

Using fold but your accumulator starts as the first element anyway? Iterator::reduce cuts out the boilerplate and handles empty iterators gracefully.

The fold pattern you keep writing

When finding the longest string, maximum value, or combining elements, fold forces you to pick an initial value — often awkwardly:

1
2
3
4
5
6
7
let words = vec!["rust", "is", "awesome"];

let longest = words.iter().fold("", |acc, &w| {
    if w.len() > acc.len() { w } else { acc }
});

assert_eq!(longest, "awesome");

That empty string "" is a code smell — it’s not a real element, it’s just satisfying fold’s signature. And if the input is empty, you silently get "" back instead of knowing there was nothing to reduce.

Enter reduce

Iterator::reduce uses the first element as the initial accumulator. No seed value needed, and it returns Option<T>None for empty iterators:

1
2
3
4
5
6
7
let words = vec!["rust", "is", "awesome"];

let longest = words.iter().reduce(|acc, w| {
    if w.len() > acc.len() { w } else { acc }
});

assert_eq!(longest, Some(&"awesome"));

The Option return makes the empty case explicit — no more silent defaults.

Finding extremes without max_by

reduce is perfect for custom comparisons where max_by feels heavy:

1
2
3
4
5
6
7
let scores = vec![("Alice", 92), ("Bob", 87), ("Carol", 95), ("Dave", 88)];

let top_scorer = scores.iter().reduce(|best, current| {
    if current.1 > best.1 { current } else { best }
});

assert_eq!(top_scorer, Some(&("Carol", 95)));

Concatenating without an allocator seed

Building a combined result from parts? reduce avoids allocating an empty starter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let parts = vec![
    String::from("hello"),
    String::from(" "),
    String::from("world"),
];

let combined = parts.into_iter().reduce(|mut acc, s| {
    acc.push_str(&s);
    acc
});

assert_eq!(combined, Some(String::from("hello world")));

Compare this to fold(String::new(), ...) — with reduce, the first String becomes the accumulator directly, saving one allocation.

reduce vs fold — when to use which

Use reduce when the accumulator is the same type as the elements and there’s no meaningful “zero” value. Use fold when you need a different return type or a specific starting value:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// reduce: same type in, same type out
let sum = vec![1, 2, 3, 4].into_iter().reduce(|a, b| a + b);
assert_eq!(sum, Some(10));

// fold: different return type (counting into a HashMap)
use std::collections::HashMap;
let counts = vec!["a", "b", "a", "c", "b", "a"]
    .into_iter()
    .fold(HashMap::new(), |mut map, item| {
        *map.entry(item).or_insert(0) += 1;
        map
    });
assert_eq!(counts["a"], 3);

reduce has been stable since Rust 1.51 — it’s the functional programmer’s best friend for collapsing iterators when the first element is your natural starting point.

60. Iterator::partition — Split a Collection in Two

Need to split a collection into two groups based on a condition? Skip the manual loop — Iterator::partition does it in one call.

The manual way

Without partition, you’d loop and push into two separate vectors:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];

let mut evens = Vec::new();
let mut odds = Vec::new();

for n in numbers {
    if n % 2 == 0 {
        evens.push(n);
    } else {
        odds.push(n);
    }
}

assert_eq!(evens, vec![2, 4, 6, 8]);
assert_eq!(odds, vec![1, 3, 5, 7]);

It works, but it’s a lot of ceremony for a simple split.

Enter partition

Iterator::partition collects into two collections in a single pass. Items where the predicate returns true go left, false goes right:

1
2
3
4
5
6
7
8
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];

let (evens, odds): (Vec<_>, Vec<_>) = numbers
    .iter()
    .partition(|&&n| n % 2 == 0);

assert_eq!(evens, vec![&2, &4, &6, &8]);
assert_eq!(odds, vec![&1, &3, &5, &7]);

The type annotation (Vec<_>, Vec<_>) is required — Rust needs to know what collections to build. You can partition into any type that implements Default + Extend, not just Vec.

Owned values with into_iter

Use into_iter() when you want owned values instead of references:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let files = vec![
    "main.rs", "lib.rs", "test_utils.rs",
    "README.md", "CHANGELOG.md",
];

let (rust_files, other_files): (Vec<_>, Vec<_>) = files
    .into_iter()
    .partition(|f| f.ends_with(".rs"));

assert_eq!(rust_files, vec!["main.rs", "lib.rs", "test_utils.rs"]);
assert_eq!(other_files, vec!["README.md", "CHANGELOG.md"]);

A practical use: triaging results

partition pairs beautifully with Result to separate successes from failures:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let inputs = vec!["42", "not_a_number", "7", "oops", "13"];

let (oks, errs): (Vec<_>, Vec<_>) = inputs
    .iter()
    .map(|s| s.parse::<i32>())
    .partition(Result::is_ok);

let values: Vec<i32> = oks.into_iter().map(Result::unwrap).collect();
let failures: Vec<_> = errs.into_iter().map(Result::unwrap_err).collect();

assert_eq!(values, vec![42, 7, 13]);
assert_eq!(failures.len(), 2);

partition has been stable since Rust 1.0 — one of those hidden gems that’s been there all along. Anytime you reach for a loop to split items into two buckets, reach for partition instead.

#057 Apr 2026

57. New Math Constants — GOLDEN_RATIO and EULER_GAMMA in std

Tired of defining your own golden ratio or Euler-Mascheroni constant? As of Rust 1.94, std ships them out of the box — no more copy-pasting magic numbers.

Before: Roll Your Own

If you needed the golden ratio or the Euler-Mascheroni constant before Rust 1.94, you had to define them yourself:

1
2
const PHI: f64 = 1.618033988749895;
const EULER_GAMMA: f64 = 0.5772156649015329;

This works, but it’s error-prone. One wrong digit and your calculations drift. And every project that needs these ends up with its own slightly-different copy.

After: Just Use std

Rust 1.94 added GOLDEN_RATIO and EULER_GAMMA to the standard consts modules for both f32 and f64:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::f64::consts::{GOLDEN_RATIO, EULER_GAMMA};

fn main() {
    // Golden ratio: (1 + √5) / 2
    let phi = GOLDEN_RATIO;
    assert!((phi * phi - phi - 1.0).abs() < 1e-10);

    // Euler-Mascheroni constant
    let gamma = EULER_GAMMA;
    assert!((gamma - 0.5772156649015329).abs() < 1e-10);

    println!("φ = {phi}");
    println!("γ = {gamma}");
}

These sit right alongside the constants you already know — PI, TAU, E, SQRT_2, and friends.

Where You’d Actually Use Them

Golden ratio shows up in algorithm design (Fibonacci heaps, golden-section search), generative art, and UI layout proportions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::f64::consts::GOLDEN_RATIO;

fn golden_section_dimensions(width: f64) -> (f64, f64) {
    let height = width / GOLDEN_RATIO;
    (width, height)
}

fn main() {
    let (w, h) = golden_section_dimensions(800.0);
    assert!((w / h - GOLDEN_RATIO).abs() < 1e-10);
    println!("Width: {w}, Height: {h:.2}");
}

Euler-Mascheroni constant appears in number theory, harmonic series approximations, and probability:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use std::f64::consts::EULER_GAMMA;

/// Approximate the N-th harmonic number using the
/// asymptotic expansion: H_n ≈ ln(n) + γ + 1/(2n)
fn harmonic_approx(n: u64) -> f64 {
    let nf = n as f64;
    nf.ln() + EULER_GAMMA + 1.0 / (2.0 * nf)
}

fn main() {
    // Exact H_10 = 1 + 1/2 + 1/3 + ... + 1/10
    let exact: f64 = (1..=10).map(|i| 1.0 / i as f64).sum();
    let approx = harmonic_approx(10);

    println!("Exact H_10:  {exact:.6}");
    println!("Approx H_10: {approx:.6}");
    assert!((exact - approx).abs() < 0.01);
}

The Full Lineup

With these additions, std::f64::consts now includes: PI, TAU, E, SQRT_2, SQRT_3, LN_2, LN_10, LOG2_E, LOG2_10, LOG10_2, LOG10_E, FRAC_1_PI, FRAC_1_SQRT_2, FRAC_1_SQRT_2PI, FRAC_2_PI, FRAC_2_SQRT_PI, FRAC_PI_2, FRAC_PI_3, FRAC_PI_4, FRAC_PI_6, FRAC_PI_8, GOLDEN_RATIO, and EULER_GAMMA. That’s a pretty complete toolkit for numerical work — all with full f64 precision, available at compile time.

#055 Apr 2026

55. floor_char_boundary — Truncate Strings Without Breaking UTF-8

Ever tried to truncate a string to a byte limit and got a panic because you sliced in the middle of a multi-byte character? floor_char_boundary fixes that.

The Problem

Slicing a string at an arbitrary byte index panics if that index lands inside a multi-byte UTF-8 character:

1
2
3
4
5
6
let s = "Héllo 🦀 world";
// This panics at runtime!
// let truncated = &s[..5]; // 'é' spans bytes 1..3, index 5 is fine here
// but what if we don't know the content?
let s = "🦀🦀🦀"; // each crab is 4 bytes
// &s[..5] would panic — byte 5 is inside the second crab!

You could scan backward byte-by-byte checking is_char_boundary(), but that’s tedious and easy to get wrong.

The Fix: floor_char_boundary

str::floor_char_boundary(index) returns the largest byte position at or before index that sits on a valid character boundary. Its counterpart ceil_char_boundary gives you the smallest position at or after the index.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let s = "🦀🦀🦀"; // each 🦀 is 4 bytes, total 12 bytes

    // We want ~6 bytes, but byte 6 is inside the second crab
    let i = s.floor_char_boundary(6);
    assert_eq!(i, 4); // rounds down to end of first 🦀
    assert_eq!(&s[..i], "🦀");

    // ceil_char_boundary rounds up instead
    let j = s.ceil_char_boundary(6);
    assert_eq!(j, 8); // rounds up to end of second 🦀
    assert_eq!(&s[..j], "🦀🦀");
}

Real-World Use: Safe Truncation

Here’s a practical helper that truncates a string to fit a byte budget, adding an ellipsis if it was shortened:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
fn truncate(s: &str, max_bytes: usize) -> String {
    if s.len() <= max_bytes {
        return s.to_string();
    }
    let end = s.floor_char_boundary(max_bytes.saturating_sub(3));
    format!("{}...", &s[..end])
}

fn main() {
    let bio = "I love Rust 🦀 and crabs!";
    let short = truncate(bio, 16);
    assert_eq!(short, "I love Rust 🦀...");
    // 'I love Rust 🦀' = 15 bytes + '...' = 18 total
    // Safe! No panics, no broken characters.

    // Short strings pass through unchanged
    assert_eq!(truncate("hi", 10), "hi");
}

No more manual boundary scanning — these two methods handle the UTF-8 dance for you.

54. Cell::update — Modify Interior Values Without the Gymnastics

Tired of writing cell.set(cell.get() + 1) every time you want to tweak a Cell value? Rust 1.88 added Cell::update — one call to read, transform, and write back.

The old way

Cell<T> gives you interior mutability for Copy types, but updating a value always felt clunky:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::cell::Cell;

fn main() {
    let counter = Cell::new(0u32);

    // Read, modify, write back — three steps for one logical operation
    counter.set(counter.get() + 1);
    counter.set(counter.get() + 1);
    counter.set(counter.get() + 1);

    assert_eq!(counter.get(), 3);
    println!("Counter: {}", counter.get());
}

You’re calling .get() and .set() in the same expression, which is repetitive and visually noisy — especially when the transformation is more complex than + 1.

Enter Cell::update

Stabilized in Rust 1.88, update takes a closure that receives the current value and returns the new one:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::cell::Cell;

fn main() {
    let counter = Cell::new(0u32);

    counter.update(|n| n + 1);
    counter.update(|n| n + 1);
    counter.update(|n| n + 1);

    assert_eq!(counter.get(), 3);
    println!("Counter: {}", counter.get());
}

One call. No repetition of the cell name. The intent — “increment this value” — is immediately clear.

Beyond simple increments

update shines when the transformation is more involved:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use std::cell::Cell;

fn main() {
    let flags = Cell::new(0b0000_1010u8);

    // Toggle bit 0
    flags.update(|f| f ^ 0b0000_0001);
    assert_eq!(flags.get(), 0b0000_1011);

    // Clear the top nibble
    flags.update(|f| f & 0b0000_1111);
    assert_eq!(flags.get(), 0b0000_1011);

    // Saturating shift left
    flags.update(|f| f.saturating_mul(2));
    assert_eq!(flags.get(), 22);

    println!("Flags: {:#010b}", flags.get());
}

Compare that to flags.set(flags.get() ^ 0b0000_0001) — the update version reads like a pipeline of transformations.

A practical example: tracking state in callbacks

Cell::update is especially handy inside closures where you need shared mutable state without reaching for RefCell:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::cell::Cell;

fn main() {
    let total = Cell::new(0i64);

    let prices = [199, 450, 85, 320, 1200];
    let discounted: Vec<i64> = prices.iter().map(|&price| {
        let final_price = if price > 500 { price * 9 / 10 } else { price };
        total.update(|t| t + final_price);
        final_price
    }).collect();

    assert_eq!(discounted, vec![199, 450, 85, 320, 1080]);
    assert_eq!(total.get(), 2134);
    println!("Prices: {:?}, Total: {}", discounted, total.get());
}

No RefCell, no runtime borrow checks, no panics — just a clean in-place update.

The signature

1
2
3
impl<T: Copy> Cell<T> {
    pub fn update(&self, f: impl FnOnce(T) -> T);
}

Note the T: Copy bound — this works because Cell copies the value out, passes it to your closure, and copies the result back in. If you need this for non-Copy types, you’ll still want RefCell.

Simple, ergonomic, and long overdue. Available since Rust 1.88.0.

#053 Mar 2026

53. element_offset — Find an Element's Index by Reference

Ever had a reference to an element inside a slice but needed its index? Before Rust 1.94, you’d reach for .position() with value equality or resort to pointer math. Now there’s a cleaner way.

The problem

Imagine you’re scanning a slice and a helper function hands you back a reference to the element it found. You know the reference points somewhere inside your slice, but you need the index — not a value-based search.

1
2
3
fn first_long_word<'a>(words: &'a [&str]) -> Option<&'a &'a str> {
    words.iter().find(|w| w.len() > 5)
}

You could call .position() with value comparison, but that re-scans the slice and compares by value — which is wasteful when you already hold the exact reference.

The solution: element_offset

<[T]>::element_offset takes a reference to an element and returns its Option<usize> index by comparing pointers, not values. If the reference points into the slice, you get Some(index). If it doesn’t, you get None.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
    let words = ["hi", "hello", "rustacean", "world"];

    // A helper hands us a reference into the slice
    let found: &&str = words.iter().find(|w| w.len() > 5).unwrap();

    // Get the index by reference identity — no value scan needed
    let index = words.element_offset(found).unwrap();

    assert_eq!(index, 2);
    assert_eq!(words[index], "rustacean");

    println!("Found '{}' at index {}", found, index);
}

Why not .position()?

.position() compares by value and has to walk the slice from the start. element_offset is an O(1) pointer comparison — it checks whether your reference falls within the slice’s memory range and computes the offset directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    let values = [10, 20, 10, 30];

    let third = &values[2]; // points at the second '10'

    // position() finds the FIRST 10 (index 0) — wrong!
    let by_value = values.iter().position(|v| v == third);
    assert_eq!(by_value, Some(0));

    // element_offset() finds THIS exact element (index 2) — correct!
    let by_ref = values.element_offset(third);
    assert_eq!(by_ref, Some(2));

    println!("By value: {:?}, By reference: {:?}", by_value, by_ref);
}

This distinction matters whenever your slice has duplicate values.

When the reference is outside the slice

If the reference doesn’t point into the slice, you get None:

1
2
3
4
5
6
7
8
fn main() {
    let a = [1, 2, 3];
    let outside = &42;

    assert_eq!(a.element_offset(outside), None);

    println!("Outside reference: {:?}", a.element_offset(outside));
}

Clean, safe, and no unsafe pointer arithmetic required. Available since Rust 1.94.0.

51. File::lock — File Locking in the Standard Library

Multiple processes writing to the same file? That’s a recipe for corruption. Since Rust 1.89, File::lock gives you OS-backed file locking without external crates.

The problem

You have a CLI tool that appends to a shared log file. Two instances run at the same time, and suddenly your log entries are garbled — half a line from one process interleaved with another. Before 1.89, you’d reach for the fslock or file-lock crate. Now it’s built in.

Exclusive locking

File::lock() acquires an exclusive (write) lock. Only one handle can hold an exclusive lock at a time — all other attempts block until the lock is released:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::fs::File;
use std::io::{self, Write};

fn main() -> io::Result<()> {
    let mut file = File::options()
        .write(true)
        .create(true)
        .open("/tmp/rustbites_lock_demo.txt")?;

    // Blocks until the lock is acquired
    file.lock()?;

    writeln!(file, "safe write from process {}", std::process::id())?;

    // Lock is released when the file is closed (dropped)
    Ok(())
}

When the File is dropped, the lock is automatically released. No manual unlock() needed — though you can call file.unlock() explicitly if you want to release it early.

Shared (read) locking

Sometimes you want to allow multiple readers but block writers. That’s what lock_shared() is for:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::fs::File;
use std::io::{self, Read};

fn main() -> io::Result<()> {
    let mut file = File::open("/tmp/rustbites_lock_demo.txt")?;

    // Multiple processes can hold a shared lock simultaneously
    file.lock_shared()?;

    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    println!("Read: {contents}");

    file.unlock()?; // explicit release
    Ok(())
}

Shared locks coexist with other shared locks, but block exclusive lock attempts. Classic reader-writer pattern, enforced at the OS level.

Non-blocking with try_lock

Don’t want to wait? try_lock() and try_lock_shared() return immediately instead of blocking:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::fs::{self, File, TryLockError};

fn main() -> std::io::Result<()> {
    let file = File::options()
        .write(true)
        .create(true)
        .open("/tmp/rustbites_trylock.txt")?;

    match file.try_lock() {
        Ok(()) => println!("Lock acquired!"),
        Err(TryLockError::WouldBlock) => println!("File is busy, try later"),
        Err(TryLockError::Error(e)) => return Err(e),
    }

    Ok(())
}

If another process holds the lock, you get TryLockError::WouldBlock instead of hanging. Perfect for tools that should fail fast rather than block when another instance is already running.

Key details

  • Advisory locks: these locks are advisory on most platforms — they don’t prevent other processes from reading/writing the file unless those processes also use locking
  • Automatic release: locks are released when the File handle is dropped
  • Cross-platform: works on Linux, macOS, and Windows (uses flock on Unix, LockFileEx on Windows)
  • Stable since Rust 1.89
#049 Mar 2026

49. std::io::pipe — Anonymous Pipes in the Standard Library

Need to wire up stdout and stderr from a child process, or stream data between threads? Since Rust 1.87, std::io::pipe() gives you OS-backed anonymous pipes without reaching for external crates.

What’s an anonymous pipe?

A pipe is a one-way data channel: one end writes, the other reads. Before 1.87, you needed the os_pipe crate or platform-specific code to get one. Now it’s a single function call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::io::{self, Read, Write};

fn main() -> io::Result<()> {
    let (mut reader, mut writer) = io::pipe()?;

    writer.write_all(b"hello from the pipe")?;
    drop(writer); // close the write end so reads hit EOF

    let mut buf = String::new();
    reader.read_to_string(&mut buf)?;
    assert_eq!(buf, "hello from the pipe");

    println!("Received: {buf}");
    Ok(())
}

pipe() returns a (PipeReader, PipeWriter) pair. PipeReader implements Read, PipeWriter implements Write — they plug into any generic I/O code you already have.

Merge stdout and stderr from a child process

The killer use case: capture both output streams from a subprocess as a single interleaved stream:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use std::io::{self, Read};
use std::process::Command;

fn main() -> io::Result<()> {
    let (mut recv, send) = io::pipe()?;

    let mut child = Command::new("echo")
        .arg("hello world")
        .stdout(send.try_clone()?)
        .stderr(send)
        .spawn()?;

    child.wait()?;

    let mut output = String::new();
    recv.read_to_string(&mut output)?;
    assert!(output.contains("hello world"));

    println!("Combined output: {output}");
    Ok(())
}

The try_clone() on the writer lets both stdout and stderr write to the same pipe. When both copies of the PipeWriter are dropped (one moved into stdout, one into stderr), reads on the PipeReader return EOF.

Why not just use Command::output()?

Command::output() captures stdout and stderr separately into Vec<u8> — you get two blobs, no interleaving, and everything is buffered in memory. With pipes, you can stream the output as it arrives, merge the two streams, or fan data into multiple consumers. Pipes give you the plumbing; output() gives you the convenience.

Key behavior

A read on PipeReader blocks until data is available or all writers are closed. A write on PipeWriter blocks when the OS pipe buffer is full. This is the same behavior as Unix pipes under the hood — because that’s exactly what they are.

#047 Mar 2026

47. Vec::pop_if — Conditionally Pop the Last Element

Need to remove the last element of a Vec only when it meets a condition? Vec::pop_if does exactly that — no index juggling, no separate check-then-pop.

The old way

Before pop_if, you’d write something like this:

1
2
3
4
5
6
let mut stack = vec![1, 2, 3, 4];

if stack.last().is_some_and(|x| *x > 3) {
    let val = stack.pop().unwrap();
    println!("Popped: {val}");
}

Two separate calls, and a subtle TOCTOU gap if you’re not careful — last() checks one thing, then pop() acts on an assumption.

Enter pop_if

Stabilized in Rust 1.86, pop_if combines the check and the removal into one atomic operation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let mut stack = vec![1, 2, 3, 4];

// Pop the last element only if it's greater than 3
let popped = stack.pop_if(|x| *x > 3);
assert_eq!(popped, Some(4));
assert_eq!(stack, [1, 2, 3]);

// Now the last element is 3 — doesn't match, so nothing happens
let stayed = stack.pop_if(|x| *x > 3);
assert_eq!(stayed, None);
assert_eq!(stack, [1, 2, 3]);

The closure receives a &mut T reference to the last element. If it returns true, the element is removed and returned as Some(T). If false (or the vec is empty), you get None.

Mutable access inside the predicate

Because the closure gets &mut T, you can even modify the element before deciding whether to pop it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let mut tasks = vec![
    String::from("buy milk"),
    String::from("URGENT: deploy fix"),
];

let urgent = tasks.pop_if(|task| {
    if task.starts_with("URGENT:") {
        *task = task.replacen("URGENT: ", "", 1);
        true
    } else {
        false
    }
});

assert_eq!(urgent.as_deref(), Some("deploy fix"));
assert_eq!(tasks, vec!["buy milk"]);

A practical use: draining from the back

pop_if is handy for processing a sorted vec from the tail. Think of a priority queue backed by a sorted vec where you only want to process items above a threshold:

1
2
3
4
5
6
7
8
9
let mut scores = vec![10, 25, 50, 75, 90];

let mut high_scores = Vec::new();
while let Some(score) = scores.pop_if(|s| *s >= 70) {
    high_scores.push(score);
}

assert_eq!(high_scores, vec![90, 75]);
assert_eq!(scores, vec![10, 25, 50]);

Clean, expressive, and no off-by-one errors. Another small addition to Vec that makes everyday Rust just a bit nicer.

#042 Mar 2026

42. array::from_fn — Build Arrays from a Function

Need a fixed-size array where each element depends on its index? Skip the vec![..].try_into().unwrap() dance — std::array::from_fn builds it in place with zero allocation.

The problem

Rust arrays [T; N] have a fixed size known at compile time, but initializing them with computed values used to be awkward:

1
2
3
4
5
6
// Clunky: build a Vec, then convert
let squares: [u64; 5] = (0..5)
    .map(|i| i * i)
    .collect::<Vec<_>>()
    .try_into()
    .unwrap();

That allocates a Vec on the heap just to get a stack array. For Copy types you could use [0u64; 5] and a loop, but that doesn’t work for non-Copy types and is verbose either way.

The fix: array::from_fn

1
2
let squares: [u64; 5] = std::array::from_fn(|i| (i as u64) * (i as u64));
assert_eq!(squares, [0, 1, 4, 9, 16]);

The closure receives the index (0..N) and returns the element. No heap allocation, no unwrapping — just a clean array on the stack.

Non-Copy types? No problem

from_fn shines when your elements don’t implement Copy:

1
2
3
let labels: [String; 4] = std::array::from_fn(|i| format!("item_{i}"));
assert_eq!(labels[0], "item_0");
assert_eq!(labels[3], "item_3");

Try doing that with [String::new(); 4] — the compiler won’t let you because String isn’t Copy.

Stateful initialization

The closure can capture mutable state. Elements are produced left to right, index 0 first:

1
2
3
4
5
6
7
let mut acc = 1u64;
let powers_of_two: [u64; 6] = std::array::from_fn(|_| {
    let val = acc;
    acc *= 2;
    val
});
assert_eq!(powers_of_two, [1, 2, 4, 8, 16, 32]);

A practical example: lookup tables

Build a compile-time-friendly lookup table for ASCII case conversion offsets:

1
2
3
4
5
6
let is_upper: [bool; 128] = std::array::from_fn(|i| {
    (i as u8).is_ascii_uppercase()
});
assert!(is_upper[b'A' as usize]);
assert!(!is_upper[b'a' as usize]);
assert!(!is_upper[b'0' as usize]);

Why it matters

std::array::from_fn has been stable since Rust 1.63. It avoids heap allocation, works with any type (no Copy or Default bound), and keeps your code readable. Anytime you reach for Vec just to build a fixed-size array — stop, and use from_fn instead.

#032 Mar 2026

32. iter::successors

Need to generate a sequence where each element depends on the previous one? std::iter::successors turns any “next from previous” logic into a lazy iterator.

1
2
3
4
5
6
7
8
use std::iter::successors;

// Powers of 10 that fit in a u32
let powers: Vec<u32> = successors(Some(1u32), |&n| n.checked_mul(10))
    .collect();

// [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000]
assert_eq!(powers.len(), 10);

You give it a starting value and a closure that computes the next element from a reference to the current one. Return None to stop — here checked_mul naturally returns None on overflow, so the iterator terminates on its own.

It works great for any recurrence:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::iter::successors;

// Collatz sequence starting from 12
let collatz: Vec<u64> = successors(Some(12u64), |&n| match n {
    1 => None,
    n if n % 2 == 0 => Some(n / 2),
    n => Some(3 * n + 1),
}).collect();

assert_eq!(collatz, vec![12, 6, 3, 10, 5, 16, 8, 4, 2, 1]);

Think of it as unfold for when your state is the yielded value. Simple, lazy, and zero-allocation until you collect.