Memory Layout and Representation

#[repr(Rust)]

#[repr(Rust)] is the default memory representation applied to every struct, enum, and union that does not carry an explicit representation attribute (#[repr(C)], #[repr(transparent)], or #[repr(u*)] / #[repr(i*)]). Note that #[repr(Rust)] is the conceptual name of the default representation in the Rust Reference; writing the literal attribute #[repr(Rust)] in source code produces a compiler error (E0552: unrecognized representation hint).

What the Default Representation Guarantees

Under the default representation, the compiler optimizes for small size, low alignment padding, and niche packing while providing only a minimal set of deterministic layout guarantees:

Type Shape Guaranteed by Default Representation Not Guaranteed Across Compiler Versions
Fields with identical alignment May still be reordered arbitrarily Declaration order of fields
Mixed-alignment struct Size is a multiple of alignment; fields do not overlap (unless ZST) Exact field offsets or total padding
Two identical generic instantiations Same layout within the same compilation artifact Same layout between two distinct nominal structs with identical fields
Zero-sized structs / unit structs Size is 0, alignment is $\ge 1$ Specific memory address of ZST references
Option<&T>, Option<NonNull<T>> Niche-optimized to match pointer size Niche layout of arbitrary multi-variant enums

Automatic Field Reordering

In a default-represented struct, rustc sorts fields (typically by decreasing alignment and niche availability) so smaller fields pack into alignment holes that would otherwise become padding.

struct SensorReading {
    status: u8,       // align 1, size 1
    timestamp: u64,   // align 8, size 8
    sensor_id: u16,   // align 2, size 2
    channel: u8,      // align 1, size 1
    reading: f32,     // align 4, size 4
}

Compiler-Selected Default Layout (16 bytes total, 0 bytes padding!):
+-----------------------+---------------+-------------+--------+---------+
|   timestamp: u64      | reading: f32  |sensor_id:u16| status | channel |
|      (8 bytes)        |   (4 bytes)   |  (2 bytes)  | (1 B)  |  (1 B)  |
+-----------------------+---------------+-------------+--------+---------+
  0..7                    8..11           12..13        14       15
use std::mem::offset_of;

struct SensorReading {
    status: u8,
    timestamp: u64,
    sensor_id: u16,
    channel: u8,
    reading: f32,
}

fn main() {
    println!("size:  {} bytes", size_of::<SensorReading>());
    println!("align: {} bytes", align_of::<SensorReading>());
    println!("timestamp offset: {}", offset_of!(SensorReading, timestamp));
    println!("reading offset:   {}", offset_of!(SensorReading, reading));
    println!("sensor_id offset: {}", offset_of!(SensorReading, sensor_id));
    println!("status offset:    {}", offset_of!(SensorReading, status));
    println!("channel offset:   {}", offset_of!(SensorReading, channel));
}
size:  16 bytes
align: 8 bytes
timestamp offset: 0
reading offset:   8
sensor_id offset: 12
status offset:    14
channel offset:   15

If SensorReading had kept its source declaration order (#[repr(C)]), its size would be 24 bytes (8 bytes of wasted padding) instead of 16 bytes.

Layout Modifiers Usable with the Default Representation

Two layout modifiers can be applied to a default-represented struct without switching to #[repr(C)]:

  1. #[repr(align(N))]: Raises the minimum alignment of the struct or enum to N (where N is a power of two).
  2. #[repr(packed)] / #[repr(packed(N))]: Lowers the alignment of all fields to 1 (or N), eliminating padding while still permitting the compiler to reorder fields.
#[repr(align(64))]
struct CacheAlignedCounter {
    count: u64,
}

fn main() {
    println!("size:  {} bytes", size_of::<CacheAlignedCounter>());
    println!("align: {} bytes", align_of::<CacheAlignedCounter>());
}
size:  64 bytes
align: 64 bytes