3

Let's say we have this code:

fn inc(src: u32) -> u32 {
    src + 1
}

fn inc2(src: u32) -> u32 {
    src + 2
}

type Incrementer = fn(u32) -> u32;
fn increment_printer(inc: Incrementer) {
    println!("{}", inc(1));
}

fn main() {
    increment_printer(inc);
    increment_printer(inc2);
}

Two functions with the same signature and a 3rd one that accepts pointers for them. Running this code results in:

2
3

being printed.

But something similar won't compile:

use core::future::Future;

async fn inc(src: u32) -> u32 {
    src + 1
}

async fn inc2(src: u32) -> u32 {
    src + 2
}

type Incrementer = fn(u32) -> dyn Future<Output = u32>;
async fn increment_printer(inc: Incrementer) {
    println!("{}", inc(1).await);
}

fn main() {
    async {
        increment_printer(inc).await;
        increment_printer(inc2).await;
    }
}
18 |         increment_printer(inc).await;
   |                           ^^^ expected trait object `dyn Future`, found opaque type
   |
   = note: expected fn pointer `fn(_) -> (dyn Future<Output = u32> + 'static)`
                 found fn item `fn(_) -> impl Future {inc}`

I know that each async function has its own type, as mentioned in the error. Is it possible to force the compiler to forget about the concrete type and treat them as similar types?

Maybe it's possible to force an async function to return a Box<Future<Output=u32>>?

I don't want to give up async functions for convenience. Also I don't want to require calling Box::pin() before passing the async function pointer to the function.

It would be interesting to know if there are more options.

3 Answers 3

8

Maybe it's possible to force an async function to return a Box<Future<Output=u32>>?

Not really, but it's possible to wrap the function into a closure that calls the function and returns a boxed future. Of course, the closure itself needs to be boxed so functions like increment_printer can receive it, but both boxings can be encapsulated in a utility function. For example (playground):

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

async fn inc(src: u32) -> u32 {
    src + 1
}

async fn inc2(src: u32) -> u32 {
    src + 2
}

type Incrementer = Box<dyn FnOnce(u32) -> Pin<Box<dyn Future<Output = u32>>>>;

fn force_boxed<T>(f: fn(u32) -> T) -> Incrementer
where
    T: Future<Output = u32> + 'static,
{
    Box::new(move |n| Box::pin(f(n)))
}

async fn increment_printer(inc: Incrementer) {
    println!("{}", inc(1).await);
}

fn main() {
    async {
        increment_printer(force_boxed(inc)).await;
        increment_printer(force_boxed(inc2)).await;
    };
}

If you can afford to make functions like increment_printer generic, then you can get rid of the outer box (playground):

// emulate trait alias
trait Incrementer: FnOnce(u32) -> Pin<Box<dyn Future<Output = u32>>> {}
impl<T> Incrementer for T
    where T: FnOnce(u32) -> Pin<Box<dyn Future<Output = u32>>>
{
}

fn force_boxed<T>(f: fn(u32) -> T) -> impl Incrementer
where
    T: Future<Output = u32> + 'static,
{
    move |n| Box::pin(f(n)) as _
}

async fn increment_printer(inc: impl Incrementer) {
    println!("{}", inc(1).await);
}

I don't want to give up async functions for convenience. Also I don't want to require calling Box::pin() before passing the async function pointer to the function.

You need to call Box::pin() somewhere - with the above the call is at least confined to one place, and you get an Incrementer type, of sorts, as the receiver of async functions passed through force_boxed().

Sign up to request clarification or add additional context in comments.

5 Comments

It works, and the API is not that bad. Still would've been ideal to not have to do the FnOnce dance. That being said, thank you, you saved me a fair amount of time.
@Dude Glad it helped - I've now edited the answer to simplify the signature of force_boxed() (it can accept a function, not an FnOnce itself), and to provide an alternative solution that doesn't require the closure to be boxed, at the cost of requiring functions like increment_printer to be generic.
Here's an idea based on this answer that allows using a function pointer for Incrementer, so you don't need generics (but you do need a macro to wrap the async fn on demand).
May I ask what if the function pointer takes references as parameters intead of u32? Is it still possible to use the same approach?
This should be required reading for async developers!!! I learned so much using this technique for my own needs, and so much about async/await in Rust became crystal clear after sorting out the wrinkles my own use-case introduced to the overall approach! A HUGE thank you @user4815162342 !!!
2

You can achieve the same thing, using the trait system:

#[async_trait]
trait Incrementor {
    async fn increment(&self, src: u32) -> u32;
}

and then implement the different incrementors as you wish.

#[async_trait]
impl Incrementor for Inc1 {
    async fn increment(&self, src: u32) -> u32 {
        src + 1
    }
}

#[derive(Default)]
struct Inc2;

#[async_trait]
impl Incrementor for Inc2 {
    async fn increment(&self, src: u32) -> u32 {
        src + 2
    }
}

This has the benefit of using static dispatch, if you control the objects invocation from the start, if not, it will still work with trait objects.

Playground

2 Comments

I think this answer misses an important requirement of the question. The question is specifically about existing async fns, which are to be accepted into a generic system. Requiring each function to also implement a trait seems like a non-starter.
I could be wrong, but I didn't read "existing function" as a strict requirement. I like this answer and appreciate the simplicity. I had a similar problem and used this approach.
1

Maybe it's possible to force an async function to return a Box<Future<Output=u32>>?

You can do so by wrapping it in an ordinary (non-async) function:

fn boxed_inc(src: u32) -> Pin<Box<dyn Future<Output = u32>>> {
    Box::pin(inc(src))
}
// Use like:
increment_printer(boxed_inc).await;

However, that would quickly get tedious if you had to write boxed_inc, boxed_inc2, etc. for all the async functions that you needed to wrap. Here's a macro that wraps it on demand:

macro_rules! force_boxed {
    ($inc:expr) => {{
        // I think the error message referred to here is spurious, but why take a chance?
        fn rustc_complains_if_this_name_conflicts_with_the_environment_even_though_its_probably_fine(src: u32) -> Pin<Box<dyn Future<Output = u32>>> {
            Box::pin($inc(src))
        }
        rustc_complains_if_this_name_conflicts_with_the_environment_even_though_its_probably_fine
    }}
}
// Use like:
increment_printer(force_boxed!(inc)).await;

Playground

This is similar to and inspired by the force_boxed function in user4815162342's answer, but in this case it has to be a macro in order to coerce the result to a function pointer instead of merely a closure.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.