Jon Kelley
Jon Kelley

Reputation: 81

Is it possible to create a type alias for async functions?

I'd like to create a type alias to promote closures to functions without the user having to think about the parameter and return signatures of their functions. This would translate this bit of code:

async fn some_func(s: &mut Context<Props>) -> VNode {
    // some function body...
}

into this bit of code:

static SomeFunc: FC<Props> = |ctx| async {
    // some function body
};

In JavaScript, this would use the const closure function definition over the regular function shorthand.

For regular functions, this works fine:

type FC<T: Props> = fn(&mut Context<T>) -> Vnode;

Then, static closures are promoted to function pointers.

However, impl Future cannot be used in a type alias (not even on 1.51 nightly), and I can't have generic trait bounds in type aliases either. I can't tell if this is possible, but am curious if there are ways for a type alias to work for async fns.

Why?

The API I'm designing takes functions as inputs (not structs or trait objects) and I'd like to make it easy to work with.

Upvotes: 6

Views: 919

Answers (2)

Tal Spektor
Tal Spektor

Reputation: 1

typealias AsyncCallBack = (() async -> Void)

Upvotes: -2

Alice Ryhl
Alice Ryhl

Reputation: 4249

You cannot do this directly, because every async fn has a different return type, even if the future evaluates to the same type. This is because each async fn has its own unique Future type that it returns.

To do this, you must use trait objects (dyn Fn) instead of function pointers (fn()) as the type. The type you are looking for is

use futures::future::BoxFuture;

type FC<T> = Box<dyn Send + Sync + for<'a> Fn(&'a mut Context<T>) -> BoxFuture<'a, Vnode>>;

The above type will act as a function that returns a Future, but a conversion step is required to go from an ordinary async fn into a function of the above type.

Performing this conversion can be done as follows:

Box::new(|context| Box::pin(my_async_fn(context)))

It can also be written using the futures crate. The version that uses .boxed() will in some cases help the type checker out and avoid some errors that the Box::pin version can give.

use futures::future::FutureExt;

Box::new(|context| my_async_fn(context).boxed())

Since the specific type alias we are using in this case has lifetimes involved, there is no way to define a convenience conversion for the above. That said, when the arguments are not references, you can do this:

use futures::future::BoxFuture;

type AsyncFnPtr = Box<dyn Send + Sync + Fn(SomeArg) -> BoxFuture<'static, RetValue>>;

fn convert<F, Fut>(func: F) -> AsyncFnPtr
where
    F: Send + Sync + 'static,
    F: Fn(SomeArg) -> Fut,
    Fut: Send + 'static,
    Fut: Future<Output = RetValue>,
{
    Box::new(|context| Box::pin(func(context)))
}

Additional resources:

  1. What does for<'a> mean?
  2. Why does the + 'static not result in a memory leak?
  3. A thread on URLO on the same topic.

Upvotes: 5

Related Questions