External Publication
Visit Post

`MaybeInvalid<T>` - separate concepts of uninitialized memory and invalid values

Rust Internals [Unofficial] March 16, 2026
Source

Currently, uninitialized memory is dealt with using a MaybeUninit<T> type. But:

  1. Unitialized memory is difficult
  2. It can also store inproperly initialized values

So it might be better to dedicate MaybeUninit to the first point only and separate the second into a distinct type.

Idea

Create a new type that can store T without proper initialization, but unlike MaybeUninit it must contain a fixed value:

| Valid value | Invalid value | Non-fixed value ---|---|---|--- MaybeUninit<T> | | | MaybeInvalid<T> | | | T | | |

#[repr(transparent)]
pub union MaybeInvalid<T> {
    invalid: (),
    value: ManuallyDrop<T>,
}

impl<T> MaybeInvalid<T> {
    ...
    // similar to MaybeUninit<T>
}

MaybeUninit would get new functions to interact with it:

impl<T> MaybeUninit<T> {
    ...
    pub fn written(MaybeInvalid<T>) -> Self;
    pub unsafe fn assume_written(self) -> MaybeInvalid<T>;
    pub unsafe fn assume_written_ref(&self) -> &MaybeInvalid<T>;
    pub unsafe fn assume_written_mut(&mut self) -> &mut MaybeInvalid<T>;
    pub fn write_raw(&mut self, MaybeInvalid<T>) -> &mut MaybeInvalid<T>;
}

Case 1: enum optimization

We could define a trait that gives a compiler "an example" of invalid value of this type:

/// ## Safety
/// INVALID's value must be actually invalid:
/// impossible to obtain in safe Rust
pub unsafe trait Invalid: Sized {
    const INVALID: MaybeInvalid<Self>;
}

// example
unsafe impl<'a, T> Invalid for &'a T {
    const INVALID: MaybeInvalid<Self> = MaybeInvalid::zeroed();
}

This could be used to optimize enums layout for arbitrary user types: for<T: Invalid> assert_eq!(size_of::<T>(), size_of::<Option<T>>)

Case 2: freezeing uninitialized values

LLVM's freeze instruction, mentioned a couple of times (17188, 22254, 13231 from a quick search) here, would become trivial and safe:

impl<T> MaybeUninit<T> {
    ...
    pub fn freeze(self) -> MaybeInvalid<T>;
}

And if we had some auto trait for always-valid types, then one could create an "uninitialized" buffer without using unsafe:

let mut buf = MaybeUninit::<[u8; 256]>::uninit().freeze().valid();

That trait would be mutually exclusive with Invalid from case 1 though, which is probably fine - there are Drop and Copy but these are fundamental.

Discussion in the ATmosphere

Loading comments...