Safe MaybeUninit initialization

One of the nice things about Option is that you can put values in it:

let x = None;
*x = Some(10);

But then you pay the cost of unwrapping it every time:

println!("{}", x.unwrap());

What if you could have the best of both worlds: safe initialization, and safe unchecked unwrap?

The QCell crate brings with it a QCell type, which, while it does take up extra memory for the owner ID, it also provides a safe wrapper around UnsafeCell. What if we could similarly provide a safe wrapper around Option::unwrap_unchecked?

Transmuting MaybeUninit<T> to T is unsound as layouts can change... but we can transmute 'id and 'static. So... maybe the solution is to brand our MaybeUninit<'id, T> with a lifetime and transmute it to 'static when we know it's fully initialized? We can also still have checked unwraps during initialization, if we want them. Is this doable? cc @steffahn

It would be possible to have a method that takes a T, writes it to the MaybeUninit, and then returns a type that behaves like &mut T, except that it runs the destructor of the value when dropped.

So like, here's the use-case: we have selfref. it is slow. in particular, it is slower than C, because the way to get to the &'selfref Foo<'selfref> requires the use of foo: Cell<Option<&'selfref Foo<'selfref>>>, and foo.get().unwrap().

we want to get it to be no-slower than C. and we want it with a safe abstraction.

What we're trying to figure out is if we can have something along the lines of:

use selfref::Holder;
use bmaybeuninit::BMaybeUninit;

let holder = BMaybeUninit::factory(|factory| {
  let holder = Box::pin(Holder::new_with(|foo| foo.build(Whatever(factory.new()))));
  holder.as_ref().operate_in(|foo| {
    foo.0.init(factory, foo)
  });
  holder
});
holder.operate_in(|foo| {
  foo.0.get().0.get(); // no .unwrap()! and ideally we can use Option::unwrap_unchecked internally for performance.
});

edit: okay we need the factory in the init calls too but anyway.

Is that true? You can't soundly transmute OuterType<MaybeUninit<T>> to OuterType<T> for an arbitrary outer type because the layout of the outer type might use a niche of T, but if there's no outer type I'm not clear on whether that's sound or not.

The array example in the MaybeUninit docs actually transmutes from an array of MaybeUninits to an array of the inner type. If that's not sound the docs probably need to be updated.

Also cannot do it for Struct<T> vs Struct<MaybeUninit<T>> because layout randomization. This rules out doing it with GATs.

But we can turn Foo<'a> into Foo<'static> for some invariant lifetime 'a.

The std docs are probably authoritative in that particular case.

The layout guarantees of MaybeUninit<T> are here.

2 Likes

how does this look? (untested)

//! Branded Option

use core::marker::PhantomData;
use core::ops::Deref;

#[repr(transparent)]
pub struct BOption<'id, T>(Option<T>, PhantomData<fn(&'id ())->&'id ()>);

impl<T: Copy> Copy for BOption<'static, T> {}
impl<T: Clone> Clone for BOption<'static, T> {
    fn clone(&self) -> Self {
        let Self(option, id) = self;
        Self(option.clone(), *id)
    }
}

pub trait Wrapper {
    type Kind<'a>;
}

pub struct Factory<'id> {
    count: usize,
    id: PhantomData<fn(&'id ())->&'id ()>,
}

impl<'id> Drop for Factory<'id> {
    fn drop(&mut self) {
        assert_eq!(self.count, 0);
    }
}

impl<'id> Factory<'id> {
    pub fn new_none<T>(&mut self) -> BOption<'id, T> {
        self.count += 1;
        BOption(None, self.id)
    }

    pub fn init<'a, T>(
        &mut self,
        boption: &'a mut BOption<'id, T>,
        value: T,
    ) -> &'a mut T {
        let was_none = boption.0.is_none();
        let value = boption.0.insert(value);
        if was_none {
            self.count -= 1;
        }
        value
    }
}

impl BOption<'static, ()> {
    pub fn factory<T, F>(f: F) -> T::Kind<'static>
    where
        T: Wrapper,
        F: for<'id> FnOnce(&mut Factory<'id>) -> T::Kind<'id>,
    {
        let mut factory = Factory {
            count: 0,
            id: PhantomData, // inferred 'static
        };
        f(&mut factory)
    }
}

impl<T> BOption<'static, T> {
    pub fn new_init(t: T) -> Self {
        Self(Some(t), PhantomData)
    }
}

impl<T> Deref for BOption<'static, T> {
    type Target = T;
    fn deref(&self) -> &T {
        // SAFETY: the safety of this is very non-local.
        // you can't create a BOption without going through Factory, which keeps
        // track of uninitialized BOptions.
        // you can't swap a Factory with another Factory since you can't get the
        // invariant 'id to converge.
        // finally, you can only gain access to a 'static BOption if it's
        // already initialized. oh and you can only swap it for other
        // initialized BOptions.
        unsafe {
            self.0.as_ref().unwrap_unchecked()
        }
    }
}

cc @steffahn again, now that we have code. (hey, it compiles at least.)

we don't know how to extend this to MaybeUninit without leaking tho.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.