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
ManuallyDrop
)T
Drop
regardless of TIt's called ManuallyDrop
, but it does not allow uninitialized memory:
ManuallyDrop<T>
is guaranteed to have the same layout and bit validity asT
, and is subject to the same layout optimizations asT
.
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!