How to use GATs?

We're trying to do this:

#![feature(generic_associated_types)]
use std::cell::Cell;
use std::cell::RefCell;
use std::marker::PhantomPinned;
use std::pin::Pin;

/// A self-referential struct.
pub trait SelfRef<'a> {
    /// An optional "drop" function.
    ///
    /// This is "equivalent" to `Drop` but nothing prevents calling it
    /// directly so don't rely on it.
    unsafe fn drop(self: Pin<&'a Self>) {
    }
}

/// An opaqueified self-referential struct "key".
pub trait Opaque {
    type Kind<'a>: SelfRef<'a>;
}

pub struct Holder<T: Opaque> {
  inner: T::Kind<'static>,
  _pinned: PhantomPinned,
}


impl<T: Opaque> Holder<T> {
    pub fn new_with(f: impl for<'a> FnOnce(&'a ()) -> T::Kind<'a>) -> Self {
        fn _ub_detect<T: Opaque>(
            f: impl for<'x> Fn(&'x ()) -> T::Kind<'x>,
            g: impl for<'x> Fn(&'x T::Kind<'x>),
        ) {
            let _arg = ();
            let _foo: T::Kind<'_> = f(&_arg);
            g(&_foo);
        }
        /// Converts T::Kind<'a> to T::Kind<'static>
        unsafe fn make_fake_static<T: Opaque>(x: T::Kind<'_>) -> T::Kind<'static> {
            std::mem::transmute(x)
        }
        Self {
            inner: unsafe { make_fake_static::<T>(f(&())) },
            _pinned: PhantomPinned
        }
    }

    pub fn operate_in<F, R>(pinned_self: Pin<&Self>, f: F) -> R
    where F: for<'a> FnOnce(Pin<&'a T::Kind<'a>>) -> R {
        /// Converts Pin<&'a T::Kind<'static>> to Pin<&'b T::Kind<'b>>
        unsafe fn downcast_static<'a, 'b, T: Opaque>(x: Pin<&'a T::Kind<'static>>) -> Pin<&'b T::Kind<'b>> {
            std::mem::transmute(x)
        }
        f(unsafe {
            downcast_static::<T>(pinned_self.map_unchecked(|self_ref| {
                &self_ref.inner
            }))
        })
    }
}

impl<T: Opaque> Drop for Holder<T> {
    fn drop(&mut self) {
        // assume it was pinned.
        Self::operate_in(unsafe { Pin::new_unchecked(&*self) }, |self_ref| {
            unsafe { SelfRef::drop(self_ref) }
        });
    }
}


struct Foo<'a> {
    x: Cell<Vec<&'a Foo<'a>>>,
    y: RefCell<usize>,
}

struct FooOpaque {
}

impl<'a> SelfRef<'a> for Foo<'a> {
    unsafe fn drop(self: Pin<&'a Self>) {
        let bar = &self.x;
        let inner = bar.replace(vec![self.get_ref()])[0];
        println!("{:p}", self.get_ref());
        println!("{:p}", inner);
        println!("{}", self.y.replace(2));
    }
}

impl Opaque for FooOpaque {
    type Kind<'a> = Foo<'a>;
}

fn basic_tests() {
    let foo = Box::pin(Holder::<FooOpaque>::new_with(|_| Foo {
        x: Cell::new(Vec::new()),
        y: RefCell::new(0),
    }));
    Holder::operate_in(foo.as_ref(), |foo| {
        let bar = &foo.x;
        bar.set(vec![foo.get_ref()]);
        println!("{}", foo.y.replace(1));
    });
    let bar = foo;
    Holder::operate_in(bar.as_ref(), |foo| {
        let bar = &foo.x;
        let inner = bar.replace(vec![foo.get_ref()])[0];
        println!("{:p}", foo.get_ref());
        println!("{:p}", inner);
        println!("{}", foo.y.replace(2));
    });
}

fn main() {
    basic_tests();
}

But we can't get it to work:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  --> src/main.rs:40:13
   |
40 |             std::mem::transmute(x)
   |             ^^^^^^^^^^^^^^^^^^^
   |
   = note: `<T as Opaque>::Kind` does not have a fixed size

error[E0597]: `_foo` does not live long enough
  --> src/main.rs:36:15
   |
36 |             g(&_foo);
   |               ^^^^^ borrowed value does not live long enough
37 |         }
   |         -
   |         |
   |         `_foo` dropped here while still borrowed
   |         borrow might be used here, when `_foo` is dropped and runs the destructor for type `<T as Opaque>::Kind<'_>`

error: aborting due to 2 previous errors

Is there any way to make it work?

Oh, we guess we can use a macro:

#![feature(generic_associated_types)]
use std::cell::Cell;
use std::cell::RefCell;
use std::marker::PhantomPinned;
use std::pin::Pin;

/// A self-referential struct.
pub trait SelfRef<'a> {
    /// An optional "drop" function.
    ///
    /// This is "equivalent" to `Drop` but nothing prevents calling it
    /// directly so don't rely on it.
    unsafe fn drop(self: Pin<&'a Self>) {
    }
}

/// An opaqueified self-referential struct "key".
pub unsafe trait Opaque {
    type Kind<'a>: SelfRef<'a>;
    unsafe fn make_fake_static(x: Self::Kind<'_>) -> Self::Kind<'static>;
    unsafe fn ub_detect(
        f: impl for<'x> Fn(&'x ()) -> Self::Kind<'x>,
        g: impl for<'x> Fn(&'x Self::Kind<'x>),
    );
}

/// Creates an opaqueified self-referential struct "key".
macro_rules! opaque {
    ( impl Opaque for $key:ty { type Kind<$l:lifetime> = $kind:ty; } ) => {
        unsafe impl Opaque for $key {
            type Kind<$l> = $kind;
            unsafe fn make_fake_static(x: Self::Kind<'_>) -> Self::Kind<'static> {
                std::mem::transmute(x)
            }
            unsafe fn ub_detect(
                f: impl for<'x> Fn(&'x ()) -> Self::Kind<'x>,
                g: impl for<'x> Fn(&'x Self::Kind<'x>),
            ) {
                let _arg = ();
                let _foo: Self::Kind<'_> = f(&_arg);
                g(&_foo);
            }
        }
    }
}

pub struct Holder<T: Opaque> {
  inner: T::Kind<'static>,
  _pinned: PhantomPinned,
}


impl<T: Opaque> Holder<T> {
    pub fn new_with(f: impl for<'a> FnOnce(&'a ()) -> T::Kind<'a>) -> Self {
        Self {
            inner: unsafe { T::make_fake_static(f(&())) },
            _pinned: PhantomPinned
        }
    }

    pub fn operate_in<F, R>(pinned_self: Pin<&Self>, f: F) -> R
    where F: for<'a> FnOnce(Pin<&'a T::Kind<'a>>) -> R {
        /// Converts Pin<&'a T::Kind<'static>> to Pin<&'b T::Kind<'b>>
        unsafe fn downcast_static<'a, 'b, T: Opaque>(x: Pin<&'a T::Kind<'static>>) -> Pin<&'b T::Kind<'b>> {
            std::mem::transmute(x)
        }
        f(unsafe {
            downcast_static::<T>(pinned_self.map_unchecked(|self_ref| {
                &self_ref.inner
            }))
        })
    }
}

impl<T: Opaque> Drop for Holder<T> {
    fn drop(&mut self) {
        // assume it was pinned.
        Self::operate_in(unsafe { Pin::new_unchecked(&*self) }, |self_ref| {
            unsafe { SelfRef::drop(self_ref) }
        });
    }
}


struct Foo<'a> {
    x: Cell<Vec<&'a Foo<'a>>>,
    y: RefCell<usize>,
}

struct FooOpaque;

opaque! {
    impl Opaque for FooOpaque {
        type Kind<'a> = Foo<'a>;
    }
}

impl<'a> SelfRef<'a> for Foo<'a> {
    unsafe fn drop(self: Pin<&'a Self>) {
        let bar = &self.x;
        let inner = bar.replace(vec![self.get_ref()])[0];
        println!("{:p}", self.get_ref());
        println!("{:p}", inner);
        println!("{}", self.y.replace(2));
    }
}

fn basic_tests() {
    let foo = Box::pin(Holder::<FooOpaque>::new_with(|_| Foo {
        x: Cell::new(Vec::new()),
        y: RefCell::new(0),
    }));
    Holder::operate_in(foo.as_ref(), |foo| {
        let bar = &foo.x;
        bar.set(vec![foo.get_ref()]);
        println!("{}", foo.y.replace(1));
    });
    let bar = foo;
    Holder::operate_in(bar.as_ref(), |foo| {
        let bar = &foo.x;
        let inner = bar.replace(vec![foo.get_ref()])[0];
        println!("{:p}", foo.get_ref());
        println!("{:p}", inner);
        println!("{}", foo.y.replace(2));
    });
}

fn main() {
    basic_tests();
}

Sadly we're gonna need '! if we wanna support more lifetimes in Kind, e.g.

struct Foo<'a, 'b> {
    x: Cell<Vec<&'a Foo<'a, 'b>>>,
    y: RefCell<usize>,
}

struct FooOpaque<'b> {
  _p: PhantomData<&'b ()>,
}

opaque! {
    impl<'b> Opaque for FooOpaque<'b> {
        type Kind<'a> = Foo<'a, 'b>;
    }
}

impl<'a, 'b> SelfRef<'a> for Foo<'a, 'b> {
    unsafe fn drop(self: Pin<&'a Self>) {
        let bar = &self.x;
        let inner = bar.replace(vec![self.get_ref()])[0];
        println!("{:p}", self.get_ref());
        println!("{:p}", inner);
        println!("{}", self.y.replace(2));
    }
}

would require Holder to carry a T::Kind<'!> because a T::Kind<'static> is impossible in this case.

How about:

/// # SAFETY
///
/// `ErasedKind` must have the exact same layout as `Self::Kind`.
pub unsafe trait Opaque {
    type Kind<'a>: SelfRef<'a>;
    type ErasedKind;
}

/// # Safety
///
/// `ErasedKind` must not be held alive longer than `kind`'s lifetime.
unsafe fn erase_kind<T: Opaque>(kind: T::Kind<'_>) -> T::ErasedKind {
    // transmute without the size check
    unsafe { mem::transmute_copy(&ManuallyDrop::new(kind)) }
}

/// # Safety
///
/// Must not be called more than once on the same value.
unsafe fn drop_erased_kind<T: Opaque>(kind: Pin<&T::ErasedKind>) {
    let kind = unsafe { mem::transmute::<Pin<&T::ErasedKind>, Pin<&T::Kind<'_>>>(kind) };
    unsafe { kind.drop() };
}

// Example for types

struct Foo<'a, 'b> {
    x: Cell<Vec<&'a Foo<'a, 'b>>>,
    y: RefCell<usize>,
}

struct FooOpaque<'b> {
  _p: PhantomData<&'b ()>,
}

unsafe impl<'b> Opaque for FooOpaque<'b> {
    type Kind<'a> = Foo<'a, 'b>;
    type ErasedKind = Foo<'b, 'b>;
}

// Holder type

pub struct Holder<T: Opaque> {
  inner: T::ErasedKind,
  _pinned: PhantomPinned,
}
// etc

playing around with this rn

#![feature(generic_associated_types)]
use std::cell::Cell;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::marker::PhantomPinned;
use std::pin::Pin;

/// A self-referential struct.
pub trait SelfRef<'a> {
    /// An optional "drop" function.
    ///
    /// This is "equivalent" to `Drop` but nothing prevents calling it
    /// directly so don't rely on it.
    unsafe fn drop(self: Pin<&'a Self>) {
    }
}

/// An opaqueified self-referential struct "key".
///
/// # Safety
///
/// ???
pub unsafe trait Opaque {
    type Kind<'a>: SelfRef<'a> where Self: 'a;
    unsafe fn make_fake_static<'a, 'b>(x: Self::Kind<'a>) -> Self::Kind<'b> where Self: 'a + 'b;
    unsafe fn ub_detect(
        f: impl for<'x> Fn(&'x ()) -> Self::Kind<'x>,
        g: impl for<'x> Fn(&'x Self::Kind<'x>),
    ) {
    }
}

/// Creates an opaqueified self-referential struct "key".
macro_rules! opaque {
    ( impl Opaque for $key:ty { type Kind<$l:lifetime> = $kind:ty; } ) => {
        unsafe impl Opaque for $key {
            type Kind<$l> = $kind;
            unsafe fn make_fake_static<'x, 'y>(x: Self::Kind<'x>) -> Self::Kind<'y> {
                std::mem::transmute(x)
            }
            unsafe fn ub_detect(
                f: impl for<'x> Fn(&'x ()) -> Self::Kind<'x>,
                g: impl for<'x> Fn(&'x Self::Kind<'x>),
            ) {
                let _arg = ();
                let _foo: Self::Kind<'_> = f(&_arg);
                g(&_foo);
            }
        }
    }
}

pub struct Holder<'a, T: Opaque + 'a> {
  inner: T::Kind<'a>,
  _pinned: PhantomPinned,
}


impl<'i, T: Opaque + 'i> Holder<'i, T> {
    pub fn new_with(f: impl for<'a> FnOnce(&'a ()) -> T::Kind<'a>) -> Self {
        Self {
            inner: unsafe { T::make_fake_static(f(&())) },
            _pinned: PhantomPinned
        }
    }

    pub fn operate_in<F, R>(pinned_self: Pin<&Self>, f: F) -> R
    where F: for<'a> FnOnce(Pin<&'a T::Kind<'a>>) -> R {
        /// Converts Pin<&'a T::Kind<'static>> to Pin<&'b T::Kind<'b>>
        unsafe fn downcast_static<'a, 'b, 'c, T: Opaque>(x: Pin<&'a T::Kind<'c>>) -> Pin<&'b T::Kind<'b>> {
            std::mem::transmute(x)
        }
        f(unsafe {
            downcast_static::<T>(pinned_self.map_unchecked(|self_ref| {
                &self_ref.inner
            }))
        })
    }
}

impl<'i, T: Opaque + 'i> Drop for Holder<'i, T> {
    fn drop(&mut self) {
        // assume it was pinned.
        Self::operate_in(unsafe { Pin::new_unchecked(&*self) }, |self_ref| {
            unsafe { SelfRef::drop(self_ref) }
        });
    }
}


//struct Foo<'a> {
//    x: Cell<Vec<&'a Foo<'a>>>,
//    y: RefCell<usize>,
//}
//struct FooOpaque;
//opaque! {
//    impl Opaque for FooOpaque {
//        type Kind<'a> = Foo<'a>;
//    }
//}
//impl<'a> SelfRef<'a> for Foo<'a> {
//    unsafe fn drop(self: Pin<&'a Self>) {
//        let bar = &self.x;
//        let inner = bar.replace(vec![self.get_ref()])[0];
//        println!("{:p}", self.get_ref());
//        println!("{:p}", inner);
//        println!("{}", self.y.replace(2));
//    }
//}

struct Foo<'a, 'b> {
    x: Cell<Vec<&'a Foo<'a, 'b>>>,
    y: RefCell<usize>,
}
struct FooOpaque<'b> {
  _p: PhantomData<&'b ()>,
}
unsafe impl<'b> Opaque for FooOpaque<'b> {
    type Kind<'a> = Foo<'a, 'b> where Self: 'a;
    unsafe fn make_fake_static<'x, 'y>(x: Self::Kind<'x>) -> Self::Kind<'y> where Self: 'x, Self: 'y {
        std::mem::transmute(x)
    }
    //unsafe fn ub_detect(
    //    f: impl for<'x> Fn(&'x ()) -> Self::Kind<'x>,
    //    g: impl for<'x> Fn(&'x Self::Kind<'x>),
    //) {
    //    let _arg = ();
    //    let _foo: Self::Kind<'_> = f(&_arg);
    //    g(&_foo);
    //}
}
impl<'a, 'b> SelfRef<'a> for Foo<'a, 'b> {
    unsafe fn drop(self: Pin<&'a Self>) {
        let bar = &self.x;
        let inner = bar.replace(vec![self.get_ref()])[0];
        println!("{:p}", self.get_ref());
        println!("{:p}", inner);
        println!("{}", self.y.replace(2));
    }
}

fn basic_tests() {
    let foo = Box::pin(Holder::<FooOpaque>::new_with(|_| Foo {
        x: Cell::new(Vec::new()),
        y: RefCell::new(0),
    }));
    Holder::operate_in(foo.as_ref(), |foo| {
        let bar = &foo.x;
        bar.set(vec![foo.get_ref()]);
        println!("{}", foo.y.replace(1));
    });
    let bar = foo;
    Holder::operate_in(bar.as_ref(), |foo| {
        let bar = &foo.x;
        let inner = bar.replace(vec![foo.get_ref()])[0];
        println!("{:p}", foo.get_ref());
        println!("{:p}", inner);
        println!("{}", foo.y.replace(2));
    });
}

fn main() {
    basic_tests();
}

can't seem to figure out how to get ub_detect to work...

yeah we figured it out :‌)

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.