Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Create type with same layout and niches but no Drop impl

I ran into the following issue today:

use std::mem::{size_of, MaybeUninit};

struct Foo<'a> {
    foo: &'a i32,
}


fn main() {
    println!("{}", size_of::<Option<Foo>>());              // 8
    println!("{}", size_of::<Option<MaybeUninit<Foo>>>()); // 16 (!)
}

So despite being #[repr(transparent)], MaybeUninit can have ‘side effects’ regarding
type layout because it apparently inhibits niche optimizations.

Is there a way for me to create a type let’s say PhantomSlot<T> that does not have this problem?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

I need a type that

  • can be constructed uninitialized (unlike e.g. ManuallyDrop)
  • has the same size, align and niches as T
  • never implements Drop regardless of T

>Solution :

It’s called ManuallyDrop, but it does not allow uninitialized memory:

ManuallyDrop<T> is guaranteed to have the same layout and bit validity as T, and is subject to the same layout optimizations as T.

use std::mem::{size_of, ManuallyDrop};

struct Foo<'a> {
    foo: &'a i32,
}

fn main() {
    println!("{}", size_of::<Option<Foo>>());              // 8
    println!("{}", size_of::<Option<ManuallyDrop<Foo>>>()); // 8
}

Your wanted type is fundamentally impossible. Consider:

use std::mem::MaybeUninit;

fn main() {
    let mut v = Some(MaybeUninit::<&i32>::uninit());
    if let Some(v) = &mut v {
        unsafe {
            std::ptr::write_bytes(v.as_mut_ptr(), 0x00, 1);
        }
    }
}

0x00 is the discriminant of None. If Option<MaybeUninit<&i32>> was niche-optimized, this code would overwrite the discriminant, making the containing Option into None! Worse, we are borrowing the enum payload (which don’t exist with None), so we invalidated our reference!

Even worse, uninitialized memory can have any bit pattern, including 0x00, so Some(MaybeUninit::uninit()) can be None! Even worse, uninitialized memory can have a variable bit pattern in different accesses, so if the compiler decided to compile code that checks the discriminant of the Option and does something based on it for some reason to two loads, they could differ, leading to arbitrary miscompilations!

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading