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 :

Note: None of the below is guaranteed, it just happens to be the case today.

Generally, yes, we store the tag in one byte. This is why for u8 the size is two bytes.

However, there are two other considerations here:

  1. The data needs to be adequately aligned and the size needs to be a multiply of the alignment (this rule has to hold for all Rust types). If we put the tag first and the data after (this is the case currently), we need padding after (or before) the tag so the data will be properly aligned. If we put the data first and the tag after, we need padding so the size is multiply of the alignment (the alignment is the same as the data’s alignment, for it to be properly aligned). Either way, we need padding, and the size of padding + the tag is the same as the alignment of the data. u16 has alignment of two bytes, u32 of four, and u64 and u128 of eight. This is the reason for their size.

  2. When we can, we prefer to pack the data and tag together, to save memory. bool has a niche, meaning it has some invalid values (all values except 0 and 1). So for Some we store the value directly, and for None we use one of the invalid values.

Leave a Reply