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 of 0 bytes and an alignment of 1 byte (unless raised with #[repr(align(N))]).
  • Zero-sized fields inside a non-empty #[repr(Rust)] struct occupy 0 bytes 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)