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
, how to specify the return type ? Vec– Charlie 木匠 Oct 28 '22 at 22:14is not working, it complains `Output ...`. Could you have a return type example ?