Impl of FnOnce is not general enough ( mixed collection of async functions )

use std::future::Future;
use std::pin::Pin;

struct Message;

trait Observer<Args> {
    fn call(&self, args: Args) -> Pin<Box<dyn Future<Output=()>>>;
}

impl<F, Fut, A1, A2> Observer<(A1, A2)> for F
    where
        F: Fn(A1, A2) -> Fut,
        Fut: Future<Output=()> + 'static
{
    fn call(&self, args: (A1, A2)) -> Pin<Box<dyn Future<Output=()>>> {
        Box::pin(self(args.0, args.1))
    }
}

pub struct Subjects {
    message_create: Vec<Box<dyn for<'a, 'b> Observer<(&'a str, &'b Message)>>>
}

impl Subjects {
    pub fn message_create(&mut self, observer: impl for<'b, 'c> Observer<(&'b str, &'c Message)> + 'static) {
        self.message_create.push(Box::new(observer));
    }
}

async fn p(token: &str, message: &Message) {}

fn main() {
    let mut subjects = Subjects { message_create: Vec::new() };
    subjects.message_create(p)
}
error: implementation of `FnOnce` is not general enough
  --> src/main.rs:61:5
   |
61 |     subjects.message_create(p)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: `for<'a, 'b> fn(&'a str, &'b Message) -> impl Future<Output = ()> {p}` must implement `FnOnce<(&'0 str, &'c Message)>`, for any lifetime `'0`...
   = note: ...but it actually implements `FnOnce<(&'1 str, &Message)>`, for some specific lifetime `'1`

I've seen some similar issues but i'm not sure how to apply solutions to my case.

Your problem is the Fut: 'static restriction. Your function async fn p(token: &str, message: &Message) returns a future type that (implicitly) contains the function arguments, which are short-lived references, and hence Fut: 'static is only met when you call this with &'static str and &'static Message arguments, too. However you’re after a general for<'b, 'c> Observer<(&'b str, &'c Message)>, so that doesn’t work.

The 'static bound comes about because you’re returning a Pin<Box<dyn Future<Output = ()>>> which is a shorthand for Pin<Box<dyn Future<Output = ()> + 'static>>. Let’s try to generalize to a Pin<Box<dyn Future<Output = ()> + 'a>> of any lifetime. This isn’t quite so straightforward, because you still need to connect this lifetime to the inputs somehow.

Something like A1: 'a, A2: 'a seems a good starting point, we can try it

impl<F, Fut, A1, A2> Observer<(A1, A2)> for F
where
    F: Fn(A1, A2) -> Fut,
    Fut: Future<Output = ()>,
{
    fn call<'a>(&self, args: (A1, A2)) -> Pin<Box<dyn Future<Output = ()> + 'a>>
    where
        A1: 'a,
        A2: 'a,
    {
        Box::pin(self(args.0, args.1))
    }
}
error[E0195]: lifetime parameters or bounds on method `call` do not match the trait declaration

alright…

trait Observer<Args> {
    fn call<'a>(&self, args: Args) -> Pin<Box<dyn Future<Output = ()> + 'a>>;
}
error[E0276]: impl has stricter requirements than trait
  --> src/main.rs:17:13
   |
7  |     fn call<'a>(&self, args: Args) -> Pin<Box<dyn Future<Output = ()> + 'a>>;
   |     ------------------------------------------------------------------------- definition of `call` from trait
...
17 |         A1: 'a,
   |             ^^ impl has extra requirement `A1: 'a`

...
18 |         A2: 'a,
   |             ^^ impl has extra requirement `A2: 'a`

this should do it:

trait Observer<Args> {
    fn call<'a>(&self, args: Args) -> Pin<Box<dyn Future<Output = ()> + 'a>>
    where
        Args: 'a;
}

actually, not quite:

error[E0309]: the parameter type `Fut` may not live long enough
  --> src/main.rs:22:9
   |
17 |     fn call<'a>(&self, args: (A1, A2)) -> Pin<Box<dyn Future<Output = ()> + 'a>>
   |             -- the parameter type `Fut` must be valid for the lifetime `'a` as defined here...
...
22 |         Box::pin(self(args.0, args.1))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `Fut` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
20 |         A2: 'a, Fut: 'a
   |               ~~~~~~~~~

now this seems unfortunate. As far as I can tell, the way this is declared, the compiler does not make the usual implications that lifetime bound from a trait can transfer to associated types. But we have a possible solution: just don’t introduce a type variable for it. We use a helper trait instead.

This pattern is already available in a general macro-generated collections of traits like this one that I've published in a crate. Let's copy the definition of that into our playground and use it:

impl<F, A1, A2> Observer<(A1, A2)> for F
where
    F: AsyncFn2<A1, A2, Output = ()>,
{
    fn call<'a>(&self, args: (A1, A2)) -> Pin<Box<dyn Future<Output = ()> + 'a>>
    where
        A1: 'a,
        A2: 'a,
    {
        Box::pin(self(args.0, args.1))
    }
}
error[E0309]: the associated type `<F as AsyncFn2<A1, A2>>::OutputFuture` may not live long enough
  --> src/main.rs:61:9
   |
56 |     fn call<'a>(&self, args: (A1, A2)) -> Pin<Box<dyn Future<Output = ()> + 'a>>
   |             -- the associated type `<F as AsyncFn2<A1, A2>>::OutputFuture` must be valid for the lifetime `'a` as defined here...
...
61 |         Box::pin(self(args.0, args.1))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `<F as AsyncFn2<A1, A2>>::OutputFuture` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
59 |         A2: 'a, <F as AsyncFn2<A1, A2>>::OutputFuture: 'a
   |               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

aaaand… I forgot that this deduction of lifetime bounds for associated types also needs Self restricted. Let’s add that

impl<F, A1, A2> Observer<(A1, A2)> for F
where
    F: AsyncFn2<A1, A2, Output = ()>,
{
    fn call<'a>(&self, args: (A1, A2)) -> Pin<Box<dyn Future<Output = ()> + 'a>>
    where
        Self: 'a,
        A1: 'a,
        A2: 'a,
    {
        Box::pin(self(args.0, args.1))
    }
}
error[E0276]: impl has stricter requirements than trait
  --> src/main.rs:58:15
   |
47 | /     fn call<'a>(&self, args: Args) -> Pin<Box<dyn Future<Output = ()> + 'a>>
48 | |     where
49 | |         Args: 'a;
   | |_________________- definition of `call` from trait
...
58 |           Self: 'a,
   |                 ^^ impl has extra requirement `F: 'a`

and also to the trait definition:

trait Observer<Args> {
    fn call<'a>(&self, args: Args) -> Pin<Box<dyn Future<Output = ()> + 'a>>
    where
        Self: 'a,
        Args: 'a;
}

And now that all works! :tada:


Note however that with current compiler limitations, you'll probably not be able to pass anything but literal async fn items to this; notably closures don’t work in my experience, the compiler doesn’t get happy with the lifetimes there (for reasons that are more just arbitrary limitations of the current compiler than anything else).

Since you're turning this into Pin<Box<dyn Future …>-returning functions anyways, you could consider accepting such a signature for message_create already. This would mean users with functions that don't box their futures yet, the caller will need to write a small wrapping closure boxing the returned future themself; however it has the huge benefit of supporting closures at all. (You could of course also consider allowing both versions of the API

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.