Data Representation ​
Updated Mar 2026How Rust lays out data in memory. Understanding repr, alignment, padding, field reordering, enum layout, and null pointer optimization is essential for FFI, performance tuning, and unsafe code.
Memory Layout Fundamentals ​
Every type in Rust has two properties that govern its memory layout:
| Property | Definition |
|---|---|
| Size | Total bytes needed to store the value |
| Alignment | Address must be a multiple of this value |
Rules:
- Alignment is always at least 1 and always a power of 2
- A type's size must always be a multiple of its alignment
- Composite types inherit the largest alignment of their fields
repr(Rust) -- Default Layout ​
The default representation gives the compiler freedom to optimize field ordering and padding.
Padding and Reordering Example ​
struct A {
a: u8, // 1 byte
b: u32, // 4 bytes, needs 4-byte alignment
c: u16, // 2 bytes, needs 2-byte alignment
}Naive layout (C-style, field order preserved):
| a (1) | pad (3) | b (4) | c (2) | pad (2) | = 12 bytesRust-optimized layout (fields reordered by alignment):
| b (4) | c (2) | a (1) | pad (1) | = 8 bytesKey Guarantees ​
- Two instances of the same struct type are always laid out identically
- Two different struct types with identical field definitions may have different layouts
- Rust reserves the right to reorder fields to minimize padding
- You cannot rely on field order in
repr(Rust)for FFI or serialization
Composite Data Types ​
| Type | Category | Description |
|---|---|---|
struct | Named product type | Named fields |
| Tuple | Anonymous product type | Positional fields |
| Array | Homogeneous product type | Same-type elements, contiguous |
enum | Named sum type | Tagged union with variants |
union | Untagged union | Overlapping fields, unsafe access |
repr(C) -- C-Compatible Layout ​
Use #[repr(C)] when you need predictable, C-compatible memory layout:
#[repr(C)]
struct Point {
x: f64, // 8 bytes at offset 0
y: f64, // 8 bytes at offset 8
}
#[repr(C)]
struct MixedFields {
a: u8, // offset 0
// 3 bytes padding
b: u32, // offset 4
c: u16, // offset 8
// 2 bytes padding
} // total: 12 bytesrepr(C) Rules ​
- Fields are laid out in declaration order (no reordering)
- Each field is placed at the next properly-aligned offset
- The struct's alignment is the maximum alignment of its fields
- The struct's size is rounded up to a multiple of its alignment
When to Use repr(C) ​
| Scenario | Use repr(C)? |
|---|---|
| FFI with C/C++ libraries | Yes -- required |
| Writing to binary file formats | Yes -- predictable layout |
| Interfacing with hardware registers | Yes -- exact offsets matter |
| Pure Rust internal structs | No -- let the compiler optimize |
| Network protocol structs | Usually yes, with repr(C, packed) |
repr(C, packed) -- No Padding ​
Removes all padding between fields:
#[repr(C, packed)]
struct Packed {
a: u8, // offset 0
b: u32, // offset 1 (unaligned!)
c: u16, // offset 5
} // total: 7 bytesWarning: Packed structs may have unaligned fields. Taking a reference to an unaligned field is undefined behavior. Use std::ptr::read_unaligned or addr_of! instead:
use std::ptr;
let p = Packed { a: 1, b: 2, c: 3 };
// WRONG: &p.b may be unaligned
// let b_ref = &p.b;
// CORRECT: read unaligned field
let b_val = unsafe { ptr::addr_of!(p.b).read_unaligned() };repr(transparent) -- Single-Field Wrappers ​
Guarantees the wrapper has the exact same layout as its single non-zero-sized field:
#[repr(transparent)]
struct Wrapper(u32);
// Wrapper has identical layout to u32
// Safe to transmute between themUsed for newtype patterns, especially in FFI where you want type safety without layout overhead.
repr(u8), repr(u16), etc. -- Enum Discriminant Types ​
Controls the size and type of the enum discriminant (tag):
#[repr(u8)]
enum Color {
Red = 0,
Green = 1,
Blue = 2,
}
// Color is exactly 1 byte
assert_eq!(std::mem::size_of::<Color>(), 1);Enum Layout ​
Discriminated Unions ​
Enums with data are stored as a discriminant (tag) followed by the payload area sized for the largest variant:
enum WebEvent {
PageLoad, // no data
KeyPress(char), // 4 bytes
Click { x: i64, y: i64 }, // 16 bytes
}
// Layout: discriminant + padding + max(0, 4, 16) bytesNull Pointer Optimization (NPO) ​
This is one of Rust's most elegant optimizations. When an enum has:
- One variant with no data (like
None) - One variant containing a non-nullable pointer type
Rust eliminates the discriminant entirely by using the null value to represent the empty variant.
use std::mem::size_of;
// Option<&T> is the same size as &T (one pointer)
assert_eq!(size_of::<Option<&u32>>(), size_of::<&u32>());
// Option<Box<T>> is the same size as Box<T>
assert_eq!(size_of::<Option<Box<u32>>>(), size_of::<Box<u32>>());Types eligible for NPO:
| Type | Why it works |
|---|---|
&T | References are never null |
&mut T | Mutable references are never null |
Box<T> | Heap pointer is never null |
Vec<T> | Internal pointer is never null |
String | Internal pointer is never null |
fn() | Function pointers are never null |
NonNull<T> | Explicitly non-null pointer |
NonZero<u32> | Value is never zero |
This means Option<Box<T>> is a zero-cost abstraction -- it has the same memory footprint as a raw pointer.
Zero-Sized Types (ZSTs) ​
Types with size 0 that take no space in memory:
struct Marker; // ZST: size = 0, align = 1
// Arrays of ZSTs take no space
let arr: [Marker; 1000] = [Marker; 1000];
assert_eq!(std::mem::size_of_val(&arr), 0);
// Common ZSTs in std:
// () -- unit type
// PhantomData<T> -- zero-cost type marker
// struct NoData; -- marker structsZSTs are useful for:
- Type-level markers (e.g.,
PhantomData<T>for variance) - State machine patterns using typestates
- Map keys where only set membership matters (
HashSet<T>isHashMap<T, ()>)
Checking Layout at Runtime ​
use std::mem;
println!("Size of i32: {} bytes", mem::size_of::<i32>());
println!("Alignment of i32: {} bytes", mem::align_of::<i32>());
println!("Size of Option<&u32>: {} bytes", mem::size_of::<Option<&u32>>());
println!("Size of Option<Box<u32>>: {} bytes", mem::size_of::<Option<Box<u32>>>());
// Verify NPO in action
assert_eq!(mem::size_of::<Option<&u32>>(), mem::size_of::<&u32>());Quick Reference ​
repr | Field Order | Padding | Use Case |
|---|---|---|---|
repr(Rust) | Compiler chooses | Optimized | Default for pure Rust |
repr(C) | Declaration order | C-compatible | FFI, binary formats |
repr(C, packed) | Declaration order | None | Wire protocols, hardware |
repr(transparent) | Same as inner | Same as inner | Newtype wrappers |
repr(u8/u16/...) | N/A (enums) | Explicit tag size | C-compatible enums |