async fn
returns an anonymous type that implements Future
, so if we want to use it as a callback, we need to convert the return value to a trait object.
I tried to write an function to do this, but I had some lifetime problems.
async fn
will return lifetime of all parameters, so the signature of callback also needs to. How can I add the lifetime to the return value of the callback?
use futures::future::{Future, FutureExt, LocalBoxFuture};
type Context = ();
type AsyncCb = Box<dyn for<'r> FnOnce(&'r Context) -> LocalBoxFuture<'r, ()>>;
fn normalize_async_cb<Fut: Future<Output = ()>>(f: for<'r> fn(&'r Context) -> Fut) -> AsyncCb
// how to add 'r for Fut? ^^^
{
let cb = move |ctx: &Context| f(ctx).boxed_local();
Box::new(cb)
}
In Rust to run code asynchronously you should start the block or function with async keyword. Async keyword transforms block of code into a state machine that implements a trait called Future. A future represents an asynchronous computation. For example Async bodies are lazy: they do nothing until they are run.
One neat result of Rust’s futures and async/awaitdesign is that all of the async callers are on the stack below the async callees. In most other languages, only the youngest async callee is on the stack, and none of the async callers.
Async functions differ in one important way: all your return types are “wrapped” into a Future. You might read the documentation about Futures in Rust and think your async function needs to look like this: This is wrong! If you’re doing this, you’re overthinking it.
It wraps a value in a future that returns ready immediately. This might seem a bit strange since Rust is usually extremely rigorous when it comes to declaring the correct types, but it’s actually a huge ergonomic boost because it automatically wraps the return types from our async functions.
Rust does not support higher-kinded polymorphism, so you need to add a lifetime parameter to the AsyncCb
type:
use futures::future::{Future, FutureExt, LocalBoxFuture};
type Context = ();
type AsyncCb<'r> = Box<dyn FnOnce(&'r Context) -> LocalBoxFuture<'r, ()> + 'r>;
fn normalize_async_cb<'r, Fut: Future<Output = ()> + 'r>(f: fn(&'r Context) -> Fut) -> AsyncCb {
let cb = move |ctx: &'r Context| f(ctx).boxed_local();
Box::new(cb)
}
Aditionally, you can avoid a Box
by returning impl
trait:
fn normalize_async_cb<'r, Fut: Future<Output = ()> + 'r>(
f: fn(&'r Context) -> Fut,
) -> impl FnOnce(&'r Context) -> LocalBoxFuture<'r, ()> {
let cb = move |ctx: &'r Context| f(ctx).boxed_local();
cb
}
(The caller can then use Box::new(normalize_async_cb(…))
as type AsyncCb
if desired.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With