Receive an async function as a parameter

Hello,

Rust async beginner here. I'm struggling with using async fns as callbacks. The callback type seems elusive and I don't understant what the error is trying to tell me. My code is:

struct Message{
  data: u32
}

pub async fn send_metric(delivery: &Message) {
    dbg!(delivery);
}

pub async fn consume_from_queue(queue_addr: &str, delivery_callback:  impl Fn(&Message) -> futures::Future<Output=()> ) -> Result<(), Error> {
    Ok(())
}

I would like to pass the first function as a parameter to the second one. Essentially I'd like to do:

consume_from_queue("queue_name", send_metric);

but the error I get is:

error[E0271]: type mismatch resolving `for<'r> <for<'_> fn(&Message) -> impl core::future::future::Future {mq_consumer::send_metric} as std::ops::FnOnce<(&'r Message,)>>::Output == (dyn core::future::future::Future<Output = ()> + 'static)`
  --> metrics/src/mq_consumer_test.rs:18:5
   |
18 |     crate::mq_consumer::consume_from_queue(&MQ_URL, crate::mq_consumer::send_metric);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected opaque type, found trait core::future::future::Future
   | 
  ::: metrics/src/mq_consumer.rs:8:14
   |
8  | pub async fn consume_from_queue(addr: &str, delivery_callback:  impl Fn(&Message) -> futures::Future<Output=()> ) -> Result<(), Error> {
   |              ------------------                                                      -------------------------- required by this bound in `mq_consumer::consume_from_queue`
   |
   = note: expected type `impl core::future::future::Future`
              found type `(dyn core::future::future::Future<Output = ()> + 'static)`

Any help would be greatly apreciated!

You should also be getting a warning about omitting the dyn keyword. Because Future is a trait, specifying it as the return type here doesn't make.

What you're trying to express here is "delivery_callback is a function that takes a &Message and returns a future of some type."

The obvious way to say this is delivery_callback: impl Fn(&Message) -> impl Future<Output=()>, but that's not legal: impl trait is not allowed in that second position.

Fortunately, in this case, impl trait is not necessary. Try this signature instead:

pub async fn consume_from_queue<F>(queue_addr: &str, delivery_callback: impl Fn(&Message) -> F ) -> Result<F::Output, Error>
where F: Future
{
7 Likes

It worked! Thanks a lot. It was a really a brain teaser. Why didn't the impl Fn(&Message) -> dyn futures::Future<Output=()> work? Is it not expressing the same?

That's because the return type of a function cannot be ?Sized.

More formally, FnOnce is declared as follows:

pub trait FnOnce<Args> {
    /// The returned type after the call operator is used.
    type Output;

    /// Performs the call operation.
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

And Fn: FnOnce, so Output remains as the same type. Because Output: Sized implicitly, we cannot have Fn() -> dyn Trait, or Fn() -> [u8], or anything that's !Sized in the return type for that matter.


Not quite, using a generic or an impl Trait, leaves the type of the object up to the compiler to figure out at compile time, meaning the type is static, and therefore Sized (Unless it's an explicitly !Sized type). While dyn Future (Or more generally dyn Trait) has a static type where the actual type is unknown until runtime. It is not dissimilar to an enum consisting of every possible type that implements Trait or Future, except for that someone downstream could implement it for their own type, so the size of it is unknown, and therefore dyn Trait: !Sized.

1 Like

More succinctly, the difference between dyn and generics (with e.g. impl) is that dyn refers to "this could by any instance of this trait, and you don't know which until runtime" while generics says "this is one specific instance of this trait, and when compiling, you know which one".

2 Likes

Ok. Thanks for the explanation. Now I get the difference between imlp/generics and dyn traits. But when I try to use it I get a type/lifetime compiliation error:

error[E0271]: type mismatch resolving `for<'r> <for<'_> fn(& ```
Message
```) -> impl core::future::future::Future {mq_consumer::send_metric} as std::ops::FnOnce<(&'r Message,)>>::Output == _`
  --> metrics/src/mq_consumer_test.rs:18:5
   |
18 |     crate::mq_consumer::consume_from_queue("queue_name", crate::mq_consumer::send_metric).await;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected bound lifetime parameter, found concrete lifetime
   | 
  ::: metrics/src/mq_consumer.rs:9:14
   |
9  | pub async fn consume_from_queue<T>(addr: &str, delivery_callback: impl Fn(&Message) -> T ) -> Result<(), MetricError> 
   |              ------------------                                                        - required by this bound in `mq_consumer::consume_from_queue`

I'm really lost on this one. What lifetime is for<'r> and for<'_> fn... ?

The for<'r> is a higher-rank trait bound. It reads for all lifetimes 'r. You usually only see them with Function inputs. In this case, the compiler is concerned about impl Fn(&'_ Message) -> T. It wants to know that T lives at least as long as &Message, because T is implied to have a dependency upon &Message. If &Message disappears, that would possibly invalidate the data within T. Make sure that the lifetimes of the two are equal to each other

2 Likes

Thanks a lot. With your explanation it was clear what the error was :smiley: . It would be nice if the compiler could tell you this stuff in a more understandable way.

1 Like

In my humble opinion, compiler engineers tend to be too robotic, and that is evident when they try to verbally explain the genius that goes on in their mind.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.