Mem

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

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

99. std::mem::replace — Swap a Value and Keep the Old One

mem::take is great until your type doesn’t have a sensible Default. That’s where mem::replace steps in — you pick what gets left behind, and you still get the old value out of a &mut.

The shape of the problem

You can’t move a value out of a &mut T. The borrow checker rightly refuses. mem::take fixes this by swapping in T::default(), but an enum with no obvious default, or a type that deliberately doesn’t implement Default, leaves you stuck.

mem::replace(dest, src) is the escape hatch: it writes src into *dest and hands you back the old value.

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

let mut greeting = String::from("Hello");
let old = mem::replace(&mut greeting, String::from("Howdy"));

assert_eq!(old, "Hello");
assert_eq!(greeting, "Howdy");

No clones, no unsafe, no Default required.

State machines without a default variant

This is where replace earns its keep. Picture a connection type where none of the variants makes a natural default — Disconnected is fine here, but it might be Error(e) somewhere else, and #[derive(Default)] would be a lie:

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

enum Connection {
    Disconnected,
    Connecting(u32),
    Connected { session: String },
}

fn finalize(conn: &mut Connection) -> Option<String> {
    match mem::replace(conn, Connection::Disconnected) {
        Connection::Connected { session } => Some(session),
        _ => None,
    }
}

let mut c = Connection::Connected { session: String::from("abc123") };
let session = finalize(&mut c);

assert_eq!(session.as_deref(), Some("abc123"));
assert!(matches!(c, Connection::Disconnected));

You get the owned String out of the Connected variant — no cloning the session, no Option<Connection> gymnastics, no unsafe.

Flushing a buffer with a fresh one

mem::take would leave behind an empty Vec with zero capacity. mem::replace lets you pre-size the replacement, which matters if you’re about to refill it:

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

struct Batch {
    items: Vec<u32>,
}

impl Batch {
    fn flush(&mut self) -> Vec<u32> {
        mem::replace(&mut self.items, Vec::with_capacity(16))
    }
}

let mut b = Batch { items: vec![1, 2, 3] };
let drained = b.flush();

assert_eq!(drained, vec![1, 2, 3]);
assert!(b.items.is_empty());
assert_eq!(b.items.capacity(), 16);

Same trick works for swapping in a String::with_capacity(...), a pre-allocated HashMap, or anything where the replacement’s shape is tuned for what comes next.

When to reach for which

mem::take when the type has a cheap, meaningful Default and you don’t care about the leftover. mem::replace when you need to control the replacement — an enum variant, a pre-sized collection, a sentinel value. Both are safe, both are O(1), and both read more clearly than the Option::take / unwrap dance.

#033 Mar 2026

33. std::mem::take

Ever tried to move a value out of a &mut reference? The borrow checker won’t let you — but std::mem::take will. It swaps the value out and leaves Default::default() in its place.

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

let mut name = String::from("Ferris");
let taken = mem::take(&mut name);

assert_eq!(taken, "Ferris");
assert_eq!(name, ""); // left with String::default()

This is especially useful when working with enum state machines where you need to consume the current state:

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

enum State {
    Running(String),
    Stopped,
}

impl Default for State {
    fn default() -> Self { State::Stopped }
}

fn reset(state: &mut State) -> Option<String> {
    match mem::take(state) {
        State::Running(data) => Some(data),
        State::Stopped => None,
    }
}

Without mem::take, you’d need .clone() or unsafe gymnastics to get the value out. See also mem::replace for when you want to specify what to leave behind instead of using Default.