rustunsafeabimemory-layout

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?

I need a type that


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!