另一种存储异步函数的方法是使用特质对象。如果您想在运行时动态地交换函数或存储一组异步函数,则此方法非常有用。为此,我们可以存储返回一个装箱的 Future
的装箱的 Fn
:
use futures::future::BoxFuture;
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
这很好用,但我们可以通过编写自定义特质并自动执行转换来使其更好:
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))
}
}
现在我们可以存储自定义trait的特征对象!
struct S {
foo: Box<dyn AsyncFn>,
}
let s = S { foo: Box::new(foo) };
s.foo.call(12).await