Reputation: 4444
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:
Box<dyn>
s for the types that I want to store in the structI 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.
Upvotes: 27
Views: 15007
Reputation: 13538
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
Upvotes: 19
Reputation: 431499
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:
Upvotes: 33