10.5 Enums and Memory Layout
Understanding enum memory representation helps with performance analysis and FFI.
10.5.1 Memory Size
An enum instance requires memory for its discriminant (tag identifying the active variant) plus enough space to hold the data of its largest variant.
// Example sizes, actual values depend on architecture and alignment enum ExampleEnum { VariantA(u8), // Size = max(size(u8), size(i64), size([u8;128])) + size(disc.) VariantB(i64), // (Likely 128 bytes + padding + discriminant size) VariantC([u8; 128]), } fn main() { // All instances of ExampleEnum have the same size, regardless of active variant. let size = std::mem::size_of::<ExampleEnum>(); println!("Size of ExampleEnum: {} bytes", size); // Likely > 128 let instance_a = ExampleEnum::VariantA(10); let instance_c = ExampleEnum::VariantC([0; 128]); //size_of_val(&instance_a) == size_of_val(&instance_c) == size_of::<ExampleEnum>() println!("Size of instance_a: {}", std::mem::size_of_val(&instance_a)); println!("Size of instance_c: {}", std::mem::size_of_val(&instance_c)); }
This consistent size simplifies memory management (e.g., storing enums in arrays) but means small variants still occupy the space needed by the largest one.
10.5.2 Optimizing Memory Usage with Box
If one variant is much larger than others and less frequently used, store its data on the heap using Box
(a smart pointer) to reduce the enum’s overall stack size.
// This enum's size is determined by the larger Box pointer + discriminant enum OptimizedEnum { VariantA(u8), VariantB(i64), VariantC(Box<[u8; 1024]>), // Data on heap, enum holds pointer } // This enum's size is determined by the large array + discriminant enum LargeEnum { VariantA(u8), VariantB(i64), VariantC([u8; 1024]), // Data stored inline } fn main() { let size_optimized = std::mem::size_of::<OptimizedEnum>(); let size_large = std::mem::size_of::<LargeEnum>(); let size_box = std::mem::size_of::<Box<[u8; 1024]>>(); // Size of a pointer println!("Size of OptimizedEnum: {} bytes", size_optimized); // Smaller println!("Size of LargeEnum: {} bytes", size_large); // Much larger (>= 1024) println!("Size of Box pointer: {} bytes", size_box); // e.g., 8 on 64-bit // Create an instance with boxed data let large_data = Box::new([0u8; 1024]); let instance = OptimizedEnum::VariantC(large_data); // 'instance' (on stack) is small; the 1024 bytes are on the heap. println!("Size of instance value: {}", std::mem::size_of_val(&instance)); }
Box<T>
: StoresT
on the heap, keeping only a pointer on the stack. Size ofBox<T>
is the pointer size.- Trade-off: Reduces stack size but adds heap allocation cost and one level of indirection for data access. Best when large variants are rare or memory savings are critical (e.g., in large collections).
Box
and smart pointers are detailed in Chapter 19.
Note on Niche Optimization: Rust can optimize layout. For instance, Option<Box<T>>
usually occupies the same space as Box<T>
, using the null
pointer state for the None
discriminant. Option<&T>
also uses the null
niche. This avoids overhead for optional pointers/references.*