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