#[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)]:
#[repr(align(N))]: Raises the minimum alignment of the struct or enum toN(whereNis a power of two).#[repr(packed)]/#[repr(packed(N))]: Lowers the alignment of all fields to1(orN), 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