Open
Description
I tried this code:
use std::mem::size_of;
fn main() {
println!("(u16, [u8; 14]) = {} bytes", size_of::<(u16, [u8; 14])>());
println!("(u16, [u8; 15]) = {} bytes", size_of::<(u16, [u8; 15])>());
println!("Option<(u16, [u8; 14])> = {} bytes", size_of::<Option<(u16, [u8; 14])>>());
println!("Option<(u16, [u8; 15])> = {} bytes", size_of::<Option<(u16, [u8; 15])>>());
let mut x: Option<(u16, [u8; 15])> = None;
let bytes: [u8; 20] = unsafe { std::mem::transmute(x) };
println!("None = {:02x?}", bytes);
x = Some((0xffff, [0xee; 15]));
let bytes: [u8; 20] = unsafe { std::mem::transmute(x) };
println!("Some = {:02x?}", bytes);
}
I expected to see this happen:
(u16, [u8; 15])
has a size of 18 bytes, because there are 17 bytes of useful data and it is rounded up due to the alignment of 2 bytes.- I expected
Option<(u16, [u8; 15])>
to also take 18 bytes due to a niche optimization.
Instead, this happened:
Option<(u16, [u8; 15])>
takes 20 bytes, and appears to be layout as:
struct Foo {
discriminant: u16,
x0: u16,
x1: [u8; 15],
padding: u8,
}
I expected the Option to leverage the byte of padding at the end of the struct to decide if it's None
or Some
instead of pre-pending a discriminant.
(u16, [u8; 14]) = 16 bytes
(u16, [u8; 15]) = 18 bytes
Option<(u16, [u8; 14])> = 18 bytes
Option<(u16, [u8; 15])> = 20 bytes
None = [00, 00, 00, 00, 54, d1, 5c, fc, b6, 7f, 00, 00, 05, 00, 00, 00, 00, 00, 00, 00]
Some = [01, 00, ff, ff, ee, ee, ee, ee, ee, ee, ee, ee, ee, ee, ee, ee, ee, ee, ee, f7]
Meta
Tested on the playground: https://play.rust-lang.org/?version=stable&mode=release&edition=2021
This is inspired by the tinyvec
crate, which is roughly layout as follows (https://docs.rs/tinyvec/latest/src/tinyvec/tinyvec.rs.html#97-103, https://docs.rs/tinyvec/latest/src/tinyvec/arrayvec.rs.html#103-107) - although the niche wouldn't apply in that case (and the smallvec
crate has a better layout):
pub enum TinyVec<T, const N: usize> {
Inline((u16, [T; N])),
Heap(Vec<T>),
}