← Back to Blog

Rust Struct Alignment and Packing Analysis

Published: July 5, 2025
Tags: rust, c, structs

Introduction

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.

System Specifications

MacBook Air M2 arm64

Background Knowledge

Struct Memory Layout

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.

Setup

Docker Environment Setup

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

Experiment

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

Test Code

// 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::());
}

Results

# 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

Summary Table

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

Conclusion

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.