Skip to content

Sub-optimal layout of Option<(u16, [u8; 15])> (missed niche optimization) #117429

Open
@gendx

Description

@gendx

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>),
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-layoutArea: Memory layout of typesC-enhancementCategory: An issue proposing an enhancement or a PR with one.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions