As a Rust newcomer, I wanted to dive deeper into the reality of struct memory layout. This article explores different struct representation modes and their impact on memory usage and alignment.
MacBook Air M2 arm64
Understanding struct memory layout is crucial for systems programming, especially when interfacing with C code or optimizing for memory usage. Different representation attributes in Rust provide fine-grained control over how structs are laid out in memory.
mkdir rust-align && cd rust-align
cat << 'EOF' > Dockerfile
FROM rust:latest
RUN apt update && apt install -y vim
WORKDIR /workspace
EOF
docker build -t rust-align .
docker run --rm -it -v "$(pwd)":/workspace rust-align
We'll test five different representation modes:
Flag Name | repr Attribute | Use Case | Build Command |
---|---|---|---|
repr_rust |
(no attribute) = repr(Rust) |
Rust-optimized safe default. Field order preserved but future reordering optimization possible. | rustc --cfg repr_rust layout.rs -o rust |
repr_c |
#[repr(C)] |
C ABI compatible. Strict declaration order and C padding rules. Safe for FFI. | rustc --cfg repr_c layout.rs -o c |
repr_packed1 |
#[repr(C, packed)] |
1-byte alignment with zero gaps. For I/O or network headers. Field references are unsafe. | rustc --cfg repr_packed1 layout.rs -o p1 |
repr_packed2 |
#[repr(C, packed(2))] |
2-byte alignment (e.g., BLE GATT protocols assuming 16-bit boundaries). | rustc --cfg repr_packed2 layout.rs -o p2 |
repr_align8 |
#[repr(C, align(8))] |
8-byte boundary enforcement. For SIMD/DMA bursts or hardware constraints. | rustc --cfg repr_align8 layout.rs -o a8 |
// layout.rs - cfg flag switches between 5 patterns
#[cfg(repr_rust)] #[derive(Debug)] struct L { a: u8, b: u32, c: u16 }
#[cfg(repr_c)] #[repr(C)] struct L { a: u8, b: u32, c: u16 }
#[cfg(repr_packed1)] #[repr(C, packed)] struct L { a: u8, b: u32, c: u16 }
#[cfg(repr_packed2)] #[repr(C, packed(2))] struct L { a: u8, b: u32, c: u16 }
#[cfg(repr_align8)] #[repr(C, align(8))] struct L { a: u8, b: u32, c: u16 }
fn main() {
println!("size = {}", std::mem::size_of::());
println!("align = {}", std::mem::align_of::());
}
# Default repr(Rust) - auto optimization (minimal padding)
rustc --cfg repr_rust layout.rs -o rust && ./rust
size = 8
align = 4
# C compatible (declaration order, C ABI)
rustc --cfg repr_c layout.rs -o c && ./c
size = 12
align = 4
# Complete packing (1-byte units)
rustc --cfg repr_packed1 layout.rs -o p1 && ./p1
size = 7
align = 1
# Specified width packing (2-byte boundary)
rustc --cfg repr_packed2 layout.rs -o p2 && ./p2
size = 8
align = 2
# Forced alignment 8
rustc --cfg repr_align8 layout.rs -o a8 && ./a8
size = 16
align = 8
Mode | Size | Alignment |
---|---|---|
repr(Rust) |
8B | 4B |
repr(C) |
12B | 4B |
repr(C, packed) |
7B | 1B |
repr(C, packed(2)) |
8B | 2B |
repr(C, align(8)) |
16B | 8B |
Rust structs can be memory-optimized based on the chosen representation mode:
Mode | Size | Alignment | Explanation |
---|---|---|---|
repr(Rust) |
8B | 4B | Rust automatically adds 1-byte padding, aligning to 8-byte boundaries |
repr(C) |
12B | 4B | Declaration order maintained: u8(1) + padding(3) + u32(4) + u16(2) + padding(2) |
packed(1) |
7B | 1B | All gaps removed, but b becomes misaligned |
packed(2) |
8B | 2B | Packed to 2-byte boundaries, total size becomes 8 |
align(8) |
16B | 8B | Padding added at the end to satisfy 8-byte alignment |
Understanding these representation modes is essential for writing efficient Rust code, especially when working with systems programming, FFI, or memory-constrained environments.