Rust Struct Memory Layout
Introduction
When you create a struct in Rust, your data doesn't just exist as abstract concepts—it must be stored somewhere in your computer's memory. How Rust organizes this memory can affect your program's performance, especially when working with large datasets or performance-critical applications.
In this guide, we'll explore how Rust lays out structs in memory, covering important concepts like alignment, padding, and techniques for optimizing your struct definitions. Understanding these concepts will help you write more efficient Rust code and give you deeper insight into how the language works under the hood.
Memory Basics
Before diving into struct layout, let's quickly refresh some memory fundamentals:
- Memory Address: A unique identifier that specifies a location in memory
- Byte: The smallest addressable unit of memory (8 bits)
- Alignment: A memory address constraint that requires data to be placed at addresses divisible by specific values
Default Struct Memory Layout in Rust
By default, Rust arranges fields in a struct in the order they are declared. Let's look at a simple example:
struct Person {
name: String,
age: u32,
is_employed: bool,
}
fn main() {
let employee = Person {
name: String::from("Alice"),
age: 30,
is_employed: true,
};
println!("Size of Person: {} bytes", std::mem::size_of::<Person>());
}
Output:
Size of Person: 32 bytes
You might wonder, "Why 32 bytes?" Let's break it down:
String
is 24 bytes (it's a fat pointer containing a pointer to the data, the length, and the capacity)u32
is 4 bytesbool
is 1 byte
That adds up to 29 bytes, but we got 32. Why? This is where memory alignment comes in.
Memory Alignment and Padding
Modern CPUs don't access memory one byte at a time—they typically read and write data in chunks (like 4 or 8 bytes). For optimal performance, data types should be aligned to addresses divisible by their size:
- An
i32
(4 bytes) should be at an address divisible by 4 - An
i64
(8 bytes) should be at an address divisible by 8
Rust adds padding (extra unused bytes) between fields to maintain proper alignment. Let's visualize this with a diagram:
The 3 bytes of padding after the boolean ensure that the overall struct size is properly aligned (to 8 bytes in this case).
Let's see another example with different field types:
struct Measurement {
byte_value: u8, // 1 byte
// 3 bytes padding here
integer_value: u32, // 4 bytes
float_value: f32, // 4 bytes
}
fn main() {
println!("Size of Measurement: {} bytes", std::mem::size_of::<Measurement>());
println!("Offset of byte_value: {} bytes", std::mem::offset_of!(Measurement, byte_value));
println!("Offset of integer_value: {} bytes", std::mem::offset_of!(Measurement, integer_value));
println!("Offset of float_value: {} bytes", std::mem::offset_of!(Measurement, float_value));
}
Output:
Size of Measurement: 12 bytes
Offset of byte_value: 0 bytes
Offset of integer_value: 4 bytes
Offset of float_value: 8 bytes
Note: To use the offset_of!
macro, you'll need to add #[feature(offset_of)]
at the top of your file and use the nightly compiler, or use the memoffset
crate.
Field Reordering for Optimization
The order of fields in your struct can significantly impact memory usage due to padding. Here's a simple rule of thumb: declare fields in decreasing order of size to minimize wasted space.
Let's compare two struct definitions:
// Inefficient ordering
struct Inefficient {
a: u8,
b: u64,
c: u16,
d: u32,
}
// Efficient ordering
struct Efficient {
b: u64,
d: u32,
c: u16,
a: u8,
}
fn main() {
println!("Size of Inefficient: {} bytes", std::mem::size_of::<Inefficient>());
println!("Size of Efficient: {} bytes", std::mem::size_of::<Efficient>());
}
Output:
Size of Inefficient: 24 bytes
Size of Efficient: 16 bytes
By reordering fields from largest to smallest, we've saved 8 bytes of memory per struct instance!
Controlling Memory Layout with Attributes
#[repr(C)]
By default, Rust doesn't guarantee field order for optimization reasons. If you need a specific memory layout (e.g., for FFI with C code), you can use the #[repr(C)]
attribute:
#[repr(C)]
struct CCompatible {
a: u8,
b: u64,
c: u16,
}
fn main() {
println!("Size of CCompatible: {} bytes", std::mem::size_of::<CCompatible>());
}
This ensures the fields are laid out in exactly the order specified, following C's layout rules.
#[repr(packed)]
If you want to eliminate padding completely (at the cost of performance), you can use #[repr(packed)]
:
#[repr(packed)]
struct PackedData {
a: u8,
b: u64,
c: u16,
}
fn main() {
println!("Size of PackedData: {} bytes", std::mem::size_of::<PackedData>());
}
Output:
Size of PackedData: 11 bytes
Warning: Packed structs can cause unaligned memory access, which might be significantly slower or even cause crashes on some architectures. Use only when necessary!
Combining Representations
You can combine attributes for more control:
#[repr(C, packed(2))]
struct CustomLayout {
a: u8,
b: u64,
c: u16,
}
This uses C-compatible layout but with 2-byte alignment.
Nested Structs
Memory layout works recursively for nested structs:
struct Point {
x: f32,
y: f32,
}
struct Rectangle {
top_left: Point,
bottom_right: Point,
}
fn main() {
println!("Size of Point: {} bytes", std::mem::size_of::<Point>());
println!("Size of Rectangle: {} bytes", std::mem::size_of::<Rectangle>());
}
Output:
Size of Point: 8 bytes
Size of Rectangle: 16 bytes
The layout of Rectangle
includes the layout of both Point
instances.
Practical Applications
Optimizing Cache Performance
Modern CPUs use caches to speed up memory access. Structs that fit within a single cache line (typically 64 bytes) can be accessed more efficiently:
// Cache-friendly struct for a simple particle system
struct Particle {
position: [f32; 3], // 12 bytes
velocity: [f32; 3], // 12 bytes
mass: f32, // 4 bytes
lifetime: f32, // 4 bytes
}
fn main() {
println!("Size of Particle: {} bytes", std::mem::size_of::<Particle>());
let particles = vec![Particle {
position: [0.0, 0.0, 0.0],
velocity: [0.0, 0.0, 0.0],
mass: 1.0,
lifetime: 10.0,
}; 1000];
// Process particles (cache-friendly access pattern)
for particle in &particles {
// Processing code would go here
}
}
Data-Oriented Design
When working with large collections, consider organizing data in a structure-of-arrays pattern instead of an array-of-structures:
// Array of structures (AoS)
struct GameObject {
position: [f32; 3],
velocity: [f32; 3],
health: f32,
name: String,
// ... many more fields
}
// Structure of arrays (SoA)
struct GameWorld {
positions: Vec<[f32; 3]>,
velocities: Vec<[f32; 3]>,
healths: Vec<f32>,
names: Vec<String>,
// ... same fields, but as separate collections
}
The SoA approach can improve cache efficiency when you only need to process certain fields at a time.
Memory Layout Analysis Tools
Rust provides several ways to inspect struct memory layout:
std::mem
Functions
fn main() {
println!("Size: {}", std::mem::size_of::<MyStruct>());
println!("Alignment: {}", std::mem::align_of::<MyStruct>());
}
External Tools
- The
rustc-demangle
tool can help understand name mangling cargo-inspect
crate provides visualization of Rust type layouts
Summary
Understanding struct memory layout in Rust allows you to:
- Optimize memory usage by ordering fields efficiently
- Improve performance by aligning data properly for CPU access
- Interface with other languages using representation attributes
- Debug memory-related issues more effectively
While Rust abstracts away many memory details, knowing how your data is laid out gives you finer control over your program's efficiency, especially in performance-critical applications.
Exercises
- Create a struct with mixed field types and predict its size before using
std::mem::size_of
. - Rearrange the fields in a struct to reduce its memory footprint.
- Create a struct that needs to be compatible with a C API using appropriate attributes.
- Design a cache-friendly data structure for a game with thousands of entities.
Additional Resources
- Rust Reference: Type Layout
- Rust Performance Book
- The
memoffset
crate for calculating field offsets - "Systems Performance" by Brendan Gregg for deeper understanding of hardware interactions
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)