另一种存储异步函数的方法是使用 trait 对象。如果您希望能够在运行时动态地换出函数,或者存储异步函数的集合,这将非常有用。为此,我们可以存储一个装箱的Fn,它返回一个装箱的Future:
use futures::future::BoxFuture; // Pin<Box<dyn Future<Output = T> + Send>>
struct S {
foo: Box<dyn Fn(u8) -> BoxFuture<'static, u8>,
}
但是,如果我们尝试初始化S,我们会立即遇到问题:
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`
错误信息很清楚。 S 期望拥有 Future,但 async 函数返回 impl Future。我们需要更新函数签名以匹配存储的 trait 对象:
fn foo(x: u8) -> BoxFuture<'static, u8> {
Box::pin(async { x * 2 })
}
这可行,但在我们要存储的每个函数中,Box::pin 会很痛苦。如果我们想将其公开给用户怎么办?
我们可以通过将函数包装在闭包中来抽象出装箱:
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
这很好用,但我们可以通过编写自定义 trait 并自动执行转换来让它变得更好:
trait AsyncFn {
fn call(&self, args: u8) -> BoxFuture<'static, u8>;
}
并为我们要存储的函数类型实现它:
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))
}
}
现在我们可以存储自定义特征的特征对象了!
struct S {
foo: Box<dyn AsyncFn>,
}
let s = S { foo: Box::new(foo) };
s.foo.call(12).await // => 24