GATs, reference count pointer and closure

With the recent stabilization of GATs, I wanted to play around with those. It so happens that I could use an abstraction over reference counted pointers.

I eventually came up with an implementation that would do the job in most cases, but I'm hitting problems with wrapping closures. This led me in a fool's errant, trying many different approaches, to no avail.

I'll use an slightly modified implementation I found on reddit in order to showcase my issue, I'll use it as it is shorter, if slightly less ergonomic.

All code is inside a test case.

First I define the abstraction:

        use std::ops::Deref;
        use std::rc::Rc;
        // define generic pointers, only changed reddit's version by adding ?Sized
        trait PointerFamily {
            type Pointer<T: ?Sized>: Deref<Target = T>; // GAT here
            fn new<T>(value: T) -> Self::Pointer<T>;
        }
        // only implement for Rc
        struct RcFamily;
        impl PointerFamily for RcFamily {
            type Pointer<T: ?Sized> = Rc<T>;
            fn new<T>(value: T) -> Self::Pointer<T> {
                Rc::new(value)
            }
        }

It works for regular types, I'll not show the tests for that.

Time to introduce closures, I first make a test with a regular old Rc, That's what I want to emulate.
Rust_analyzer is providing the types, which I copied here.

        // test naked rc with a closure
        struct Bar(Rc<dyn Fn(i32) -> i32>);
        impl Bar {
            fn wrap<T: Fn(i32) -> i32 + 'static>(t: T) -> Self {
                Self(Rc::new(t))
            }
        }
        let _a = Bar::wrap(|a| 1 + a).0; // _a: Rc<dyn Fn(i32) -> i32>

Let's see how this fares with our traits:

        // test generic pointer with a closure
        let _a = RcFamily::new(|a: i32| -> i32 { a + 1 }); // _a: Rc<|i32| -> i32> ... no good
        let a: Rc<dyn Fn(i32) -> i32> = RcFamily::new(|a| a + 1); // fixed by coercing to dyn Fn
        let b: <RcFamily as PointerFamily>::Pointer<dyn Fn(i32) -> i32> = RcFamily::new(|a| a + 1); // do it generically
        let _v = vec![a, b]; // Same type

I have to specify the result type to be a dyn Fn for variable a, For variable b I try to be a bit more generic, but I still have to make use of the concrete RcFamilly type.

Now I try to do this from a function, and that's where i have an issue :

 // do it in a generic struct
        struct Baz<P: PointerFamily>(P::Pointer<dyn Fn(i32) -> i32>);
        impl<P: PointerFamily> Baz<P> {
            fn new(t: P::Pointer<dyn Fn(i32) -> i32>) -> Self {
                Self(t)
            }
            fn wrap<T: Fn(i32) -> i32 + 'static>(t: T) -> Self {
                // error[E0308]: mismatched types
                //    --> src\lib.rs:149:76
                //     |
                // 148 |             fn wrap<T: Fn(i32) -> i32 + 'static>(t: T) -> Self {
                //     |                     - this type parameter
                // 149 |                 let p: <P as PointerFamily>::Pointer<dyn Fn(i32) -> i32> = P::new(t);
                //     |                        -------------------------------------------------   ^^^^^^^^^ expected trait object `dyn Fn`, found type parameter `T`
                //     |                        |
                //     |                        expected due to this
                //     |
                //     = note: expected associated type `<P as PointerFamily>::Pointer<dyn Fn(i32) -> i32>`
                //                found associated type `<P as PointerFamily>::Pointer<T>`
                //     = help: type parameters must be constrained to match other types
                //     = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

                // let p = P::new(t);
                // let p: <P as PointerFamily>::Pointer<dyn Fn(i32) -> i32> = P::new(t);
                // Self(p)
                todo!()
            }
        }
        type RcBaz = Baz<RcFamily>;
        type Fam = RcFamily;
        let _a = RcBaz::new(Fam::new(|a| a + 2)).0; // _a: Rc<dyn Fn(i32) -> i32>

The new function works alright, but can't find a way to do the same coercing I did earlier. It looks like my types are maybe too general, or the type inference can't do it, or most likely I'm just not knowledgeable enough to find the correct incantation.

So there it is, how can I enclose a dyn Fn in my generic pointer type from a function ? How to do the coercion ?

Code on the rust playground

I believe CoerceUnsized is the piece you're missing. Unfortunately it's still unstable, but you can at least make your code work on nightly with it.

Playground

#![feature(coerce_unsized)]
use std::ops::{CoerceUnsized, Deref};
use std::rc::Rc;
// define generic pointers, only changed reddit's version by adding ?Sized
trait PointerFamily {
    type Pointer<T: ?Sized>: Deref<Target = T>; // GAT here
    fn new<T>(value: T) -> Self::Pointer<T>;
}
// only implement for Rc
struct RcFamily;
impl PointerFamily for RcFamily {
    type Pointer<T: ?Sized> = Rc<T>;
    fn new<T>(value: T) -> Self::Pointer<T> {
        Rc::new(value)
    }
}
// do it in a generic struct
struct Baz<P: PointerFamily>(P::Pointer<dyn Fn(i32) -> i32>);
impl<P: PointerFamily> Baz<P> {
    fn new(t: P::Pointer<dyn Fn(i32) -> i32>) -> Self {
        Self(t)
    }
    fn wrap<T: Fn(i32) -> i32 + 'static>(t: T) -> Self
    where
        P::Pointer<T>: CoerceUnsized<P::Pointer<dyn Fn(i32) -> i32>>,
    {
        let p: <P as PointerFamily>::Pointer<dyn Fn(i32) -> i32> = P::new(t);
        Self(p)
    }
}

Depending on how you wanted to use it, you might be able to specify conversion operations yourself to make it work on stable.

2 Likes

Ah ! I've been eying it but shied away !

Thanks

Depending on how you wanted to use it, you might be able to specify conversion operations yourself to make it work on stable.

Do you mean by implementing From for my closure type ?

I don't think you could use From, but yeah something like that. Basically roll your own CoerceUnsized to implement manually on the pointer types you need, for the type conversions you need (or put them as methods on PointerFamily).

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.