Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing an async function that calls another async function

Tags:

rust

I am trying to create a async function which takes a function pointer as a parameter. It does some stuff, calls the function, awaits on the result, then does some more stuff:

use std::future::Future;

async fn run_another_async_fn<F, Fut>(f: F)
where
    Fut: Future<Output = ()>,
    F: FnOnce(&mut i32) -> Fut,
{
    let mut i = 42;
    println!("running function");
    f(&mut i).await;
    println!("ran function");
}

async fn foo(i: &mut i32) {}

async fn bar() {
    run_another_async_fn(foo);
}

[view on Rust Playground]

Unfortunately this fails to compile:

error[E0308]: mismatched types
  --> src/lib.rs:17:5
   |
17 |     run_another_async_fn(foo);
   |     ^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected associated type `<for<'_> fn(&mut i32) -> impl Future {foo} as FnOnce<(&mut i32,)>>::Output`
              found associated type `<for<'_> fn(&mut i32) -> impl Future {foo} as FnOnce<(&mut i32,)>>::Output`
   = note: the required lifetime does not necessarily outlive the empty lifetime
note: the lifetime requirement is introduced here
  --> src/lib.rs:6:28
   |
6  |     F: FnOnce(&mut i32) -> Fut,
   |                            ^^^

Firstly, it seems the compiler found exactly what it expected but it's complaining anyway?

Secondly, what's "the empty lifetime"? I guess it must mean the '_, does that have some special significance?

Finally, what's the way to get this to compile?

like image 565
Phil Frost Avatar asked Nov 07 '22 00:11

Phil Frost


1 Answers

The issue is that there is no way to specify the same lifetime for F and Fut in the where clause.

Luckily (if you don't mind heap allocating the future) there is an easy workaround. You can use the already existing futures::future::BoxFuture; which looks like:

pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

With its help you can specify the same lifetime parameter for both the borrow and as a trait bound for the future:

 where  for<'a> F: FnOnce(&'a mut i32) -> BoxFuture<'a, ()>,

You also have to add an adapter function which will have the correct return type - i.e. BoxFuture<'_, T> instead of impl Future:

fn asd(i: &mut i32) -> BoxFuture<'_, ()> {
    foo(i).boxed()
}

or use a closure:

run_another_async_fn(|i| foo(i).boxed());

As a result your code would look like:

use futures::future::BoxFuture;
use futures::FutureExt;
use std::future::Future;

async fn run_another_async_fn<F>(f: F)
where
    for<'a> F: FnOnce(&'a mut i32) -> BoxFuture<'a, ()>,
{
    let mut i = 42;
    println!("running function");

    f(&mut i).await;

    println!("ran function");
}

fn asd(i: &mut i32) -> BoxFuture<'_, ()> {
    foo(i).boxed()
}

async fn foo<'a>(i: &'a mut i32) {
    // no-op
}

async fn bar() {
    run_another_async_fn(asd);
    run_another_async_fn(|i| foo(i).boxed());
}
like image 101
Svetlin Zarev Avatar answered Nov 26 '22 14:11

Svetlin Zarev