#131 May 10, 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.

← Previous 129. BTreeMap::extract_if — Range-Scoped Filter-and-Remove in One Pass Next → 132. abs_diff — Subtract Without Caring Which Side Is Bigger