Storing an async fn callback in a struct, that has a reference argument

Hi,

I'm trying to understand the compiler errors I'm getting when trying to store an async function callback in a struct.
In some cases, I'm getting an error of type "one type is more general than the other", and can't find a workaround.

Here's a simplified example (Rust Playground):

async fn test(arg: &u32) {
    println!("test {}", arg);
}

struct AsyncFn<F>
{
    func: F,
}

impl<F, AsyncResult> AsyncFn<F>
where
    F: FnMut(&u32) -> AsyncResult,
    AsyncResult: std::future::Future,
{
    fn new(func: F) -> Self {
        Self {
            func,
        }
    }
}

fn main() {
    let mut async_fn = AsyncFn::new(test);
}

which generates the following error:

error[E0308]: mismatched types
  --> src/lib.rs:23:24
   |
23 |     let mut async_fn = AsyncFn::new(test);
   |                        ^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected associated type `<for<'_> fn(&u32) -> impl for<'_> Future {test} as FnOnce<(&u32,)>>::Output`
              found associated type `<for<'_> fn(&u32) -> impl for<'_> Future {test} as FnOnce<(&u32,)>>::Output`

I think it's related to the lifetime of the Future, as it relates to the argument passed in...
I tried adding HRTBs to the FnMut constraint, but it didn't change anything... maybe I did it wrong?

What I find confusing, is that if I remove the reference on the u32 argument (and change the FnMut signature accordingly), then the code compiles without errors (playground).

p.s. I'm able to write a working version of this code by introducing another generic type argument (playground), but this approach doesn't work in my actual use-case, so I would really like to find a fix that keeps the FnMut signature as close as possible to the initial example.

I could be wrong, but I'd guess it's because Futures compile down to structs under the hood, and that means storing a reference with a specific lifetime (which is a kind of generic) in a struct.

If you specify &'static u32 it works. If you try &'a it doesn't and produces the same error, which references the Future trait and its Output associated type.

I would guess this has something to do with GATs, and that because &'a is a kind of generic, that it is needed as part of the Output associated type.

Thanks for your suggestions!
Although it works in this sample, using the 'static lifetime doesn't work in my real world use-case.

I would just avoid the borrow for this. u32 implements Copy so it there isn't much overhead to using the value instead of a reference.

True, the example is a bit contrived :slight_smile:
The type used in my actual code is a shared struct that doesn't implement Copy.

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.