Struggling with lifetimes for generic async function impl

Hi! I'm trying to do the following (playground link):

use futures;

trait Foo {
    type Bar<'a>;
}

impl Foo for String {
    type Bar<'a> = &'a str;
}

trait Callback<T> where T: Foo {
    fn call_it(self, a: T, c: T::Bar<'_>) -> impl Future<Output = ()>;
}

impl<T, F, A> Callback<T> for F 
where 
    T: Foo,
    F: FnOnce(T, T::Bar<'_>) -> A,
    A: Future<Output = ()>
{
    fn call_it(self, a: T, c: T::Bar<'_>) -> impl Future<Output = ()>{
        (self)(a, c)
    }
}

async fn my_callback(a: String, b: &str) {
    todo!("{}, {}", a, b);
}


fn main() {
    let value = "asdf".to_string();
    futures::executor::block_on(my_callback.call_it(value, value.as_ref()));
}

But I'm getting the following error:

Compiling playground v0.0.1 (/playground)
error[E0599]: the method `call_it` exists for fn item `fn(String, &str) -> impl Future<Output = ()> {my_callback}`, but its trait bounds were not satisfied
  --> src/main.rs:33:45
   |
33 |     futures::executor::block_on(my_callback.call_it(value, value.as_ref()));
   |                                             ^^^^^^^
   |
note: the following trait bounds were not satisfied:
      `<&for<'a> fn(String, &'a str) -> impl futures::Future<Output = ()> {my_callback} as FnOnce<(_, <_ as Foo>::Bar<'_>)>>::Output = _`
      `<&mut for<'a> fn(String, &'a str) -> impl futures::Future<Output = ()> {my_callback} as FnOnce<(_, <_ as Foo>::Bar<'_>)>>::Output = _`
      `<for<'a> fn(String, &'a str) -> impl futures::Future<Output = ()> {my_callback} as FnOnce<(_, <_ as Foo>::Bar<'_>)>>::Output = _`
  --> src/main.rs:18:33
   |
15 | impl<T, F, A> Callback<T> for F 
   |               -----------     -
...
18 |     F: FnOnce(T, T::Bar<'_>) -> A,
   |                                 ^ unsatisfied trait bound introduced here
   = help: items from traits can only be used if the trait is implemented and in scope
note: `Callback` defines an item `call_it`, perhaps you need to implement it
  --> src/main.rs:11:1
   |
11 | trait Callback<T> where T: Foo {
   | ^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0599`.
error: could not compile `playground` (bin "playground") due to 1 previous error

I've tried adding various higher ranked trait bounds to satisfy the compiler but with no luck :confused:

Hopefully this specific invocation wasn't part of your goals:

    futures::executor::block_on(my_callback.call_it(value, value.as_ref()));
    //                                              ^^^^^^^^^^^^^^^^^^^^^

Because you can't pass something that isn't Copy by value and by reference at the same time.


Otherwise, the problem in the code is similar to this recent thread: A cannot represent a borrow-capturing return type, and you need it to.

impl<T, F, A> Callback<T> for F 
where 
    T: Foo,
    // This can't accept functions that return futures that capture the
    // lifetime here ---vv
    F: FnOnce(T, T::Bar<'_>) -> A,
    A: Future<Output = ()>

async fn returns and -> impl ... capture all generics in scope, and your bound is short for:

    F: for<'any> FnOnce(T, T::Bar<'any>) -> A

Unlike the other thread, at least in this example, you don't need more bounds on the future. So AsyncFnOnce can solve the playground.

impl<T, F> Callback<T> for F 
where 
    T: Foo,
    F: AsyncFnOnce(T, T::Bar<'_>),
{

In slightly different circumstances, an alternative might have been to accept a single lifetime for the T::Bar<'_> argument to the closure. But being the associated type rules that out (I think), and besides, that also changes the semantics (you can't pass in local borrows to the closure if you do that).

1 Like

In case it's useful, here's how you can accomplish that approach by adding a lifetime to the trait (which may cause other annoyances). My instinct is to go with the AsyncFnOnce version, but I don't know your actual use case.

Using AsyncFnOnce seems to work for my use case! Didn't know that it existed :sweat_smile:, will read up on it a bit more. Thank you!

Hopefully this specific invocation wasn't part of your goals:

   futures::executor::block_on(my_callback.call_it(value, value.as_ref()));
   //                                              ^^^^^^^^^^^^^^^^^^^^^

Because you can't pass something that isn't Copy by value and by reference at the same time.

Oops, yeah that's not necessary for my goals.

1 Like