Dude
Dude

Reputation: 96

Rust: Passing async function pointers

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\n3 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.

Upvotes: 3

Views: 6181

Answers (3)

Med Aouadhi
Med Aouadhi

Reputation: 41

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

Upvotes: 2

trent
trent

Reputation: 27885

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.

Upvotes: 1

user4815162342
user4815162342

Reputation: 154846

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().

Upvotes: 8

Related Questions