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

#132 May 2026

132. abs_diff — Subtract Without Caring Which Side Is Bigger

Subtracting two unsigned integers and the smaller one comes first? Instant panic. a.abs_diff(b) returns the gap as a u* regardless of which side is bigger — no branching, no overflow.

The Problem

Unsigned subtraction in Rust panics in debug and wraps in release the moment the result would go negative. You end up writing the same branch over and over:

1
2
3
4
5
6
fn gap(a: u32, b: u32) -> u32 {
    if a > b { a - b } else { b - a }
}

assert_eq!(gap(10, 3), 7);
assert_eq!(gap(3, 10), 7);

It works, but it’s noise. And the same trick on signed integers has a sneakier bug: i32::MIN.abs_diff(i32::MAX) overflows an i32 — the gap doesn’t fit in the signed range.

After: abs_diff

Every integer type carries an abs_diff method that returns the unsigned gap directly. Signed inputs come back as the matching unsigned type, so the result always fits:

1
2
3
4
5
6
assert_eq!(10u32.abs_diff(3), 7);
assert_eq!(3u32.abs_diff(10), 7);

// Signed → unsigned, no overflow at the extremes
assert_eq!((-5i32).abs_diff(5), 10u32);
assert_eq!(i32::MIN.abs_diff(i32::MAX), u32::MAX);

No if, no checked_sub, no casting through i64 to dodge overflow. One call, one number.

Where It Earns Its Keep

Distance-style calculations are the obvious fit — anywhere “how far apart are these” is the real question and the sign is incidental:

1
2
3
4
5
6
fn manhattan(a: (i32, i32), b: (i32, i32)) -> u32 {
    a.0.abs_diff(b.0) + a.1.abs_diff(b.1)
}

assert_eq!(manhattan((1, 2), (4, 6)), 7);
assert_eq!(manhattan((-3, -3), (3, 3)), 12);

It also cleans up timestamp deltas, where one side is “now” and the other could be in the past or the future:

1
2
3
4
5
let scheduled: u64 = 1_700_000_000;
let actual:    u64 = 1_699_999_995;

let drift = scheduled.abs_diff(actual);
assert_eq!(drift, 5);

Whenever you catch yourself writing if a > b { a - b } else { b - a }, reach for abs_diff instead.

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