In this lesson, you will learn how to shrink your data structures by packing bits into smaller spaces and aligning them to satisfy CPU hardware requirements.
The Cost of Padding
In Rust, the compiler follows strict rules for memory alignment. Every type has an alignment requirement (usually the size of its largest member). If you define a struct like this:
struct User {
active: bool, // 1 byte
id: u64, // 8 bytes
}
You might expect this struct to be 9 bytes. However, because u64 must start at an address divisible by 8, the compiler inserts 7 bytes of "padding" after active. The struct actually occupies 16 bytes. In memory-constrained systems, this bloat adds up quickly when you have millions of objects.
Bit-Packing to the Rescue
Bit-packing allows us to squeeze multiple small values into a single storage unit. Instead of using a bool (which takes a full byte) and other small flags, we can use a single integer as a bitfield.
struct CompressedFlags {
// We pack 8 flags into a single byte using a bitmask
bits: u8,
}
impl CompressedFlags {
fn is_active(&self) -> bool {
(self.bits & 0b0000_0001) != 0
}
}
By manually masking bits, we avoid the overhead of individual type alignment. If you need to store complex data, consider reordering your struct fields from largest to smallest. This "reordering" strategy helps the compiler fill the gaps between large fields with smaller types, reducing the total padding bytes automatically.
Safety and Lifetimes
When packing data, remember that your lifetime constraints still apply. If you are packing references or indices into a packed buffer, ensure your PhantomData or lifetime markers remain valid. You aren't just saving space; you are potentially breaking the compiler’s ability to track memory safety if you cast raw bits into pointers. Always use repr(C) or repr(packed) cautiously, as repr(packed) removes all padding but can cause performance penalties—or even crashes—on architectures that don't allow unaligned memory access.
Key Takeaway: Reorder struct fields from largest to smallest to minimize automatic padding, and use bit-masks to consolidate small flags into single bytes to maximize memory efficiency.