Soundness review: Self-referential cell environment

This is an idea we had after making selfref, we figured we could glue it together with qcell to make fully-featured (mutable) "borrowing" environments, like so:

use qcell::{LCell, LCellOwner};
use selfref::Holder;

use std::pin::Pin;

/// A self-referential environment of cells (mutable), with zero runtime cost.
pub struct SRCellEnvironment<'k, T: selfref::Opaque + 'k> {
    inner: Holder<'k, T>,
}

impl<'k, T: selfref::Opaque> SRCellEnvironment<'k, T> {
    /// Creates a new self-referential cell environment.
    pub fn new_with<F: selfref::NewWith<'k, T>>(f: F) -> Self
    where
        T::Kind<'k>: Sized
    {
        Self {
            inner: Holder::new_with(f),
        }
    }
}

impl<'k, T: selfref::Opaque> SRCellEnvironment<'k, T> {
    /// Operates in a self-referential cell environment.
    pub fn operate_in<'i, F, R>(self: Pin<&'i mut Self>, f: F) -> R
    where
        F: OperateIn<'k, T, Out = R>
    {
        struct OperateInWrapper<F>(F);
        impl<'k, T, F> selfref::OperateIn<'k, T> for OperateInWrapper<F>
        where F: OperateIn<'k, T>, T: selfref::Opaque + 'k {
            type Out = F::Out;
            fn operate_in<'a>(self, x: Pin<&'a T::Kind<'a>>) -> Self::Out where 'k: 'a {
                let OperateInWrapper(this) = self;
                // SAFETY? we're actually unsure if this is sound!
                // however, SRCellEnvironment::operate_in requires a &mut, so
                // we believe this should be sound. we're effectively tying the
                // LCellOwner's lifetime to the &mut, and then we use Drop to
                // prevent storing an LCellOwner inside the SRCellEnvironment.
                let guard = unsafe {
                    generativity::Guard::new(generativity::Id::new())
                };
                this.operate_in(LCellOwnerWrapper(LCellOwner::new(guard)), x)
            }
        } 
        unsafe {
            self.map_unchecked_mut(|this| &mut this.inner)
        }.into_ref().operate_in(OperateInWrapper(f))
    }
}

pub trait OperateIn<'k, T: selfref::Opaque + 'k> {
    /// The value returned by this operation.
    type Out;
    /// Do this operation.
    fn operate_in<'a>(self, owner: LCellOwnerWrapper<'a>, x: Pin<&'a T::Kind<'a>>) -> Self::Out where 'k: 'a;
}

pub struct LCellOwnerWrapper<'a>(LCellOwner<'a>);

impl<'a> Drop for LCellOwnerWrapper<'a> {
    #[inline]
    fn drop(&mut self) {
    }
}

impl<'id> LCellOwnerWrapper<'id> {
    pub fn cell<T>(&self, value: T) -> LCell<'id, T> {
        self.0.cell(value)
    }
    pub fn ro<'a, T: ?Sized>(&'a self, lc: &'a LCell<'id, T>) -> &'a T {
        self.0.ro(lc)
    }
    pub fn rw<'a, T: ?Sized>(&'a mut self, lc: &'a LCell<'id, T>) -> &'a mut T {
        self.0.rw(lc)
    }
}

#[test]
fn test() {
    use std::cell::Cell;
    use std::marker::PhantomData;
    trait Foo {
        fn update(&mut self);
    }
    trait Bar {
        fn get(&self) -> String;
    }
    struct MyThing;
    impl Foo for MyThing {
        fn update(&mut self) {
            println!("hello world!");
        }
    }
    impl Bar for MyThing {
        fn get(&self) -> String {
            return String::from("hello");
        }
    }
    struct IntrusiveLCell<'a, T: ?Sized> {
        y: Cell<Option<&'a LCell<'a, dyn Bar>>>,
        x: LCell<'a, T>,
    }
    struct IntrusiveLCellKey<T: ?Sized>(PhantomData<T>);
    // SAFETY: T cannot refer to 'a so there's no unsoundness there. also the
    // struct is a pretty basic self-ref struct with no Drop impl or glue which
    // would otherwise make this unsound.
    // FIXME figure out how to get selfref::opaque! to work with T: ?Sized
    unsafe impl<T: ?Sized> selfref::Opaque for IntrusiveLCellKey<T> {
        type Kind<'a> = IntrusiveLCell<'a, T>;
    }

    let mut holder = Box::pin(
        SRCellEnvironment::<IntrusiveLCellKey<MyThing>>::new_with(
            selfref::new_with_closure::<IntrusiveLCellKey<MyThing>, _>(|_| {
                IntrusiveLCell {
                    x: LCell::new(MyThing),
                    y: Cell::new(None),
                }
            })
        )
    );

    struct operate_in;
    impl<'k> OperateIn<'k, IntrusiveLCellKey<MyThing>> for operate_in {
        type Out = ();
        fn operate_in<'a>(self, mut owner: LCellOwnerWrapper<'a>, x: Pin<&'a IntrusiveLCell<'a, MyThing>>) where 'k: 'a {
            x.y.set(Some(&x.get_ref().x));
            owner.rw(&x.x).update();
            println!("{}", owner.ro(x.y.get().unwrap()).get());
        }
    }

    holder.as_mut().operate_in(operate_in);

    let mut holder_dyn: Pin<Box<SRCellEnvironment<IntrusiveLCellKey<dyn Foo>>>> = holder;
}

We do have a few ergonomic caveats because closures are still awkward. But from what we can tell it does work?

For context, here is the full past discussion on the idea: qcell + selfref? · Issue #37 · uazu/qcell · GitHub

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.