How can I store an async function in a struct and call it from a struct instance?
I'm trying to achieve this with the new async
/await
syntax, std::future::Future
s and a recent version of Tokio. I'm using Tokio 0.2.0-alpha.4
and Rust 1.39.0-nightly
.
Different things I've tried include:
- using
Box<dyn>
s for the types that I want to store in the struct - using generics in the struct definition
I couldn't quite get a minimal working version, so here's a simplified version of what I'm trying to achieve:
async fn foo(x: u8) -> u8 {
2 * x
}
// type StorableAsyncFn = Fn(u8) -> dyn Future<Output = u8>;
struct S {
f: StorableAsyncFn,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let s = S { f: foo };
let out = (s.f)(1).await;
Ok(())
}
Of course this code fails to compile with the following error:
error[E0412]: cannot find type `StorableAsyncFn` in this scope
StorableAsyncFn
is not defined here, it's the type I'm trying to define.
Solution 1:
Let's use this as our Minimal, Reproducible Example:
async fn foo(x: u8) -> u8 {
2 * x
}
struct S {
foo: (),
}
async fn example() {
let s = S { foo };
}
It produces the error:
error[E0308]: mismatched types
--> src/main.rs:10:17
|
10 | let s = S { foo };
| ^^^ expected (), found fn item
|
= note: expected type `()`
found type `fn(u8) -> impl std::future::Future {foo}`
The type of foo
is a function pointer that takes a u8
and returns some type implementing the trait std::future::Future
. async fn
is effectively just syntax sugar that transforms -> Foo
into -> impl Future<Output = Foo>
.
We make our struct generic and place a trait bound on the generic that matches. In real code, you'd probably want to place a constraint on the the Output
associated type, but it's not needed for this example. We can then call the function like any other callable member field:
async fn foo(x: u8) -> u8 {
2 * x
}
struct S<F>
where
F: std::future::Future,
{
foo: fn(u8) -> F,
}
impl<F> S<F>
where
F: std::future::Future,
{
async fn do_thing(self) {
(self.foo)(42).await;
}
}
async fn example() {
let s = S { foo };
s.do_thing().await;
}
To be even more flexible, you could use another generic to store a closure, instead of forcing only a function pointer:
struct S<C, F>
where
C: Fn(u8) -> F,
F: std::future::Future,
{
foo: C,
}
impl<C, F> S<C, F>
where
C: Fn(u8) -> F,
F: std::future::Future,
{
async fn do_thing(self) {
(self.foo)(42).await;
}
}
See also:
- How do I call a function through a member variable?
- How do I store a closure in a struct in Rust?
Solution 2:
Another way to store an async function is with trait objects. This is useful if you want to be able to swap out the function dynamically at runtime, or store a collection of async functions. To do this, we can store a boxed Fn
that returns a boxed Future
:
use futures::future::BoxFuture; // Pin<Box<dyn Future<Output = T> + Send>>
struct S {
foo: Box<dyn Fn(u8) -> BoxFuture<'static, u8>,
}
However, if we try to initialize S
, we immediately run into a problem:
async fn foo(x: u8) -> u8 {
x * 2
}
let s = S { foo: Box::new(foo) };
error[E0271]: type mismatch resolving `<fn(u8) -> impl futures::Future {foo} as FnOnce<(u8,)>>::Output == Pin<Box<(dyn futures::Future<Output = u8> + 'static)>>`
--> src/lib.rs:14:22
|
5 | async fn foo(x: u8) -> u8 {
| -- the `Output` of this `async fn`'s found opaque type
...
14 | let s = S { foo: Box::new(foo) };
| ^^^^^^^^^^^^^ expected struct `Pin`, found opaque type
|
= note: expected struct `Pin<Box<(dyn futures::Future<Output = u8> + 'static)>>`
found opaque type `impl futures::Future`
The error message is pretty clear. S
expects a owned Future
, but async
functions return impl Future
. We need to update our function signature to match the stored trait object:
fn foo(x: u8) -> BoxFuture<'static, u8> {
Box::pin(async { x * 2 })
}
That works, but it would be a pain to Box::pin
in every single function we want to store. And what if we want to expose this to users?
We can abstract the boxing away by wrapping the function in a closure:
async fn foo(x: u8) -> u8 {
x * 2
}
let s = S { foo: Box::new(move |x| Box::pin(foo(x))) };
(s.foo)(12).await // => 24
This works fine, but we can make it even nicer by writing a custom trait and performing the conversion automagically:
trait AsyncFn {
fn call(&self, args: u8) -> BoxFuture<'static, u8>;
}
And implement it for the function type we want to store:
impl<T, F> AsyncFn for T
where
T: Fn(u8) -> F,
F: Future<Output = u8> + 'static,
{
fn call(&self, args: u8) -> BoxFuture<'static, u8> {
Box::pin(self(args))
}
}
Now we can store a trait object of our custom trait!
struct S {
foo: Box<dyn AsyncFn>,
}
let s = S { foo: Box::new(foo) };
s.foo.call(12).await // => 24