Memory Layout: Struct Padding, Field Reordering, #[repr(C)], #[repr(packed)]
- Last updated
- Prerequisites:
- Rust structs and types
- Understanding of memory alignment and C ABI
- rust
- memory-layout
- repr-c
- repr-packed
- padding
- alignment
Read at your depth
The practical view
Rust structs have a default layout: the compiler can REORDER fields to minimize padding (size = sum of field sizes, rounded up to alignment). The layout is unspecified by default — you can't rely on field order or offsets. #[repr(C)] gives C-compatible layout: fields in declaration order, with C's alignment/padding rules — required for FFI and for matching C structs. #[repr(packed)] removes padding (fields tightly packed) — saves memory but can cause unaligned access (and performance/UB issues on some platforms). #[repr(align(N))] forces a minimum alignment. Use std::mem::size_of and offset_of to check layouts.
The same idea in other frameworks
N/A — Rust language feature.N/A — Rust feature.N/A — Rust feature.N/A — Rust feature.Legacy vs modern
Default repr(Rust) layout vs #[repr(C)]
The default layout reorders fields to pack tightly; #[repr(C)] preserves declaration order with C padding — needed for FFI.
struct Point { x: u8, y: u64, z: u8 } // ❌ OLD: order NOT guaranteed#[repr(C)]
struct Point { x: u8, y: u64, z: u8 } // ✅ NEW: C layout, field order fixedInterview gotchas
Context
This tests the layout model — interviewers want the field-reordering freedom and why only #[repr(C)] provides the ABI guarantee C code needs.
The mechanical answer
The default repr(Rust) layout gives the compiler (rustc + LLVM) the freedom to reorder fields and choose padding to minimize size — an optimization that can change between compiler versions. Because the layout is unspecified and unstable, passing such a struct across an FFI boundary is unsafe: the C side expects a specific layout (declaration order with C alignment/padding), and a reordered Rust struct would be misinterpreted. #[repr(C)] pins the layout to the C rules: exact field order, C alignment, and trailing padding — making the struct ABI-compatible. The same concern applies to serializing structs directly to bytes, reading binary formats, or relying on offset_of for a specific member. If you need a stable layout for any of those, use #[repr(C)] (or repr(packed)/repr(align) as appropriate).
Trap
The naive answer is 'struct fields are in declaration order'. Rust's default layout is explicitly unspecified and may reorder. Another trap: thinking size_of alone tells you the layout — two different layouts can have the same size; offsets matter. Candidates who explain the reordering freedom and the FFI guarantee show real understanding.
Context
This probes the unaligned-access problem — interviewers want the alignment-1 consequence and the UB risk on strict-alignment architectures, plus the safe workaround.
The mechanical answer
#[repr(packed)] forces every field to alignment 1 — no padding between fields, giving the smallest possible size. The consequence: fields can sit at unaligned offsets (e.g. a u32 at offset 1). Taking a reference to such a field (&s.field) creates a reference whose address is not aligned to the field's required alignment. On strict-alignment architectures (ARM, and x86 in some SIMD contexts), dereferencing an unaligned reference is undefined behavior — the compiler can miscompile or the CPU can fault. This is why Rust emits a warning when you take a reference to a packed field's member. The safe approach: read/write the field with read_unaligned/write_unaligned (or copy the struct to an aligned location). Use packed layouts deliberately for network/protocol structs and FFI, not casually for 'smaller structs'.
Trap
The naive answer is 'packed just saves memory, no downside'. It introduces unaligned field access that is UB when dereferenced via a reference on strict-alignment targets. Another trap: thinking x86 makes it always safe — x86 tolerates unaligned loads for most instructions, but the UB contract still lets the optimizer assume alignment, and some operations (atomics, SIMD) fault. Candidates who explain the alignment-1 consequence and read_unaligned show real depth.