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:
-
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, andu64
andu128
of eight. This is the reason for their size. -
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 forSome
we store the value directly, and forNone
we use one of the invalid values.