Search code examples
memoryrustoption-type

Memory overhead of `Option` in Rust is not constant


Using the following snippet

use std::mem;

fn main() {
   println!("size Option(bool): {} ({})", mem::size_of::<Option<bool>>(), mem::size_of::<bool>());
   println!("size Option(u8): {} ({})", mem::size_of::<Option<u8>>(), mem::size_of::<u8>());
   println!("size Option(u16): {} ({})", mem::size_of::<Option<u16>>(), mem::size_of::<u16>());
   println!("size Option(u32): {} ({})", mem::size_of::<Option<u32>>(), mem::size_of::<u32>());
   println!("size Option(u64): {} ({})", mem::size_of::<Option<u64>>(), mem::size_of::<u64>());
   println!("size Option(u128): {} ({})", mem::size_of::<Option<u128>>(), mem::size_of::<u128>())
}

I see on my 64-bits machine:

size Option(bool): 1 (1)
size Option(u8): 2 (1)
size Option(u16): 4 (2)
size Option(u32): 8 (4)
size Option(u64): 16 (8)
size Option(u128): 24 (16)

So the overhead is not constant and goes up to 8 bytes. I wonder why the overhead is not just one byte to store the tag? I also wonder what representation is chosen by the compiler?


Solution

  • The Rust Reference on type layouts comes into play here:

    [...] The size of a value is always a multiple of its alignment. [...]

    The only data layout guarantees made by [the default] representation are those required for soundness. They are: [...]

    1. The alignment of the type is at least the maximum alignment of its fields.

    So the size of Option<T> must be rounded up to the nearest alignment of T, even if only one byte (or even one bit) is used to store the information of "value is present".

    The exception is types that allow for "null pointer optimization", where Option<T> has the same size of T because it can represent None by using one of the invalid states of T. For example, bool only has two states, so the compiler will optimize and use one of the remaining 254 1-byte states to represent None for Option<bool>. This works for bool, &U, &mut U, fn, Box<U>, NonZero* and NonNull<U>.