Type inference issue with closures returning a future

Hi there! :wave:

I have the following use case: I would like to store async closures as callbacks in struct and execute them later. For that matter I use futures_util::futures::BoxFuture which boils down to pinned and boxed future.

In order to register a new callback, I provide a function with a Generic bound:

fn register_cb<F>(cb: F)
where
    F: std::ops::FnMut() -> BoxFuture<'static, ()>,

Using this method leads to an compile error when not directly defining the closure in the function call:

use futures_util::future::BoxFuture;

fn register_cb<F>(cb: F)
where
    F: std::ops::FnMut() -> BoxFuture<'static, ()>,
{
    // use cb and execute it somehow
}

fn main() {
    // works:
    register_cb(|| {
        Box::pin(async {
            // async code
        })
    });

    // throws error:  expected struct `Pin<Box<(dyn futures_util::Future<Output = ()> + std::marker::Send + 'static)>>`
    //                found struct `Pin<Box<impl futures_util::Future<Output = [async output]>>>`
    let cb = async || {
        Box::pin(async {
            // async code
        })
    };

    register_cb(cb);
}

Is there something I am missing about the generic trait bound?

Also I wanted to ask if this is a proper way to store an async callback, as long as async closures are unstable, the syntax || Box::pin(async {...}) is also quite verbose. I am writing a library and thinking about writing a macro so that the user don't always need to write the Box::pin call.

Thanks a lot! :slight_smile:

It seems like the type inference just isn't smart enough to determine that cb is supposed to return a BoxFuture instead of a Pin<Box<[anonymous future]>>. There's a couple different ways you can hint it to the compiler:

let cb = || -> BoxFuture<_> {
    Box::pin(async {
        // async code
    })
};
let cb = || {
    Box::pin(async {
        // async code
    }) as BoxFuture<_>
};
2 Likes

That's why it is a bit better to be using ::futures's FutureExt-extension-trait-provided .boxed() adaptor, that shall do the coercion-to-dyn for you:

let cb = || async move {
    ...
}.boxed();
3 Likes

Ahh great, that is definitely less verbose than the Box::pin() as ... way. Thanks a lot :wink: Do you think storing the futures that way makes sense?

1 Like

Yes: storage needs a unified type, hence the need for dyn(amic(ally dispatched)) type unification, hence the dyn Future, + ownership, hence the Box, + Pin<&mut Self>-receiver in Future's API requires that outer Pin atop it, and finally a + Send as a nice default of thread-safety :slightly_smiling_face:

The only area with some wiggle room could be taking a impl Future directly (since a Future already "starts suspended", so a FnOnce() -> Future is a shape with an "unnecessary" level of suspension), but maybe you've simplified the input to the callback, and it's actually non-empty, in which case the FnOnce(…) -> layer would make a lot of sense.

Finally, provided you have no lifetime-infected parameters in the callback (e.g., because you have no parameters at all), you could also consider bounding the callback with one of the "async closure" trait aliases from the

crate, so that you can perform the Pin<Box<dyn-ing yourself, as a callee, rather than requiring each caller to do it:

fn register_cb<F> (cb: F)
where
    F : 'static + AsyncFn0<Output = ()>,
{
    let fut = f().boxed();
    // …
}

register_cb(|| async move {
    // …
});

Hope it helps :slightly_smiling_face:

1 Like

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.