Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a Rust async fn that takes a reference as a callback?

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)
}
like image 698
s97712 Avatar asked Nov 16 '19 13:11

s97712


People also ask

How to run code asynchronously in rust?

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.

Why are async callers on the stack in rust?

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.

What is the difference between async and futures in rust?

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.

What is the use of return type in rust?

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.


1 Answers

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

like image 103
Anders Kaseorg Avatar answered Sep 28 '22 00:09

Anders Kaseorg