Memory Layout and Representation
Struct Layout
Struct layout determines the memory offsets of each field inside a struct, the alignment of the composite type, and the total byte size including internal and trailing padding.
Default Struct Layout vs. C Struct Layout
By default, structs use the #[repr(Rust)] layout algorithm, which allows the compiler to reorder fields to minimize wasted padding bytes. When annotated with #[repr(C)], the compiler preserves the exact declaration order of fields using the platform C ABI layout rules.
Declaration:
struct Mixed { a: u8, b: u32, c: u16 }
#[repr(C)] Layout (12 bytes, align 4):
+-------+-------------------+-------+-------+-------+-------+---------+---------+
| a: u8 | pad (3 bytes) | b: u32 | c: u16 | pad (2) |
+-------+-------------------+-------+-------+-------+-------+---------+---------+
+0 +1..+3 +4..+7 +8..+9 +10..+11
#[repr(Rust)] Reordered Layout (8 bytes, align 4):
+-------+-------+-------+-------+---------+---------+-------+---------+
| b: u32 | c: u16 | a: u8 | pad (1) |
+-------+-------+-------+-------+---------+---------+-------+---------+
+0..+3 +4..+5 +6 +7
Inspecting Field Offsets with std::mem::offset_of!
The std::mem::offset_of! macro (stabilized in Rust 1.77) computes the compile-time byte offset of a field from the start of its enclosing struct without creating an instance.
use std::mem::offset_of;
struct DefaultOrder {
a: u8,
b: u32,
c: u16,
}
#[repr(C)]
struct COrder {
a: u8,
b: u32,
c: u16,
}
fn main() {
println!(
"DefaultOrder (size = {}, align = {}): a=@{}, b=@{}, c=@{}",
size_of::<DefaultOrder>(),
align_of::<DefaultOrder>(),
offset_of!(DefaultOrder, a),
offset_of!(DefaultOrder, b),
offset_of!(DefaultOrder, c),
);
println!(
"COrder (size = {}, align = {}): a=@{}, b=@{}, c=@{}",
size_of::<COrder>(),
align_of::<COrder>(),
offset_of!(COrder, a),
offset_of!(COrder, b),
offset_of!(COrder, c),
);
}
DefaultOrder (size = 8, align = 4): a=@6, b=@0, c=@4
COrder (size = 12, align = 4): a=@0, b=@4, c=@8
Tuple Structs and Unit Structs
- Tuple structs (
struct Pair(u8, u32);) follow the exact same layout rules as named-field structs. - Unit structs (
struct Tag;) and structs containing only zero-sized fields have a size of0bytes and an alignment of1byte (unless raised with#[repr(align(N))]). - Zero-sized fields inside a non-empty
#[repr(Rust)]struct occupy0bytes and can share the same byte offset as another field.
use std::marker::PhantomData;
use std::mem::offset_of;
struct WithZeroSized {
id: u64,
_marker: PhantomData<*const u8>,
tag: u8,
}
fn main() {
println!("size: {}", size_of::<WithZeroSized>());
println!("offset of id: {}", offset_of!(WithZeroSized, id));
println!("offset of _marker: {}", offset_of!(WithZeroSized, _marker));
println!("offset of tag: {}", offset_of!(WithZeroSized, tag));
}
size: 16
offset of id: 0
offset of _marker: 9
offset of tag: 8
Summary of Struct Variants
| Struct Kind | Example | Default Size | Default Alignment |
|---|---|---|---|
| Unit struct | struct Unit; |
0 |
1 |
| Single-field newtype | struct Id(u64); |
size_of::<u64>() (8) |
align_of::<u64>() (8) |
Multi-field #[repr(Rust)] |
struct S { a: u8, b: u32 } |
Reordered + padded (8) |
max(align_of(fields)) (4) |
Multi-field #[repr(C)] |
#[repr(C)] struct S { a: u8, b: u32 } |
Sequential + padded (8) |
max(align_of(fields)) (4) |