如何将异步函数存储在结构中并从结构实例中调用它?
Posted
技术标签:
【中文标题】如何将异步函数存储在结构中并从结构实例中调用它?【英文标题】:How can I store an async function in a struct and call it from a struct instance? 【发布时间】:2020-01-30 02:12:14 【问题描述】:我正在尝试使用新的async
/await
语法、std::future::Future
s 和最新版本的 Tokio 来实现这一点。我正在使用 Tokio 0.2.0-alpha.4
和 Rust 1.39.0-nightly
。
我尝试过的不同方法包括:
将Box<dyn>
s 用于我想要存储在结构中的类型
在结构定义中使用泛型
我无法获得最小的工作版本,所以这是我想要实现的简化版本:
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(())
当然,这段代码编译失败,错误如下:
error[E0412]: cannot find type `StorableAsyncFn` in this scope
StorableAsyncFn
此处未定义,这是我要定义的类型。
【问题讨论】:
【参考方案1】:另一种存储异步函数的方法是使用 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
【讨论】:
这个答案启发了我查找BoxFuture
并解决了我手头的问题。谢谢!【参考方案2】:
让我们用它作为我们的Minimal, Reproducible Example:
async fn foo(x: u8) -> u8
2 * x
struct S
foo: (),
async fn example()
let s = S foo ;
它会产生错误:
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`
foo
的类型是一个函数指针,它接受 u8
并返回一些实现特征 std::future::Future
的类型。 async fn
实际上只是将-> Foo
转换为-> impl Future<Output = Foo>
的语法糖。
我们将结构体设为泛型,并将特征绑定到匹配的泛型上。在实际代码中,您可能希望对 Output
关联类型施加约束,但本示例不需要它。然后我们可以像调用任何其他可调用成员字段一样调用该函数:
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;
为了更加灵活,您可以使用另一个泛型来存储闭包,而不是只强制使用函数指针:
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;
另见:
How do I call a function through a member variable? How do I store a closure in a struct in Rust?【讨论】:
感谢您的超清晰回答。另外,很高兴知道您可以将通用参数设为Future
,而无需指定Output
。以上是关于如何将异步函数存储在结构中并从结构实例中调用它?的主要内容,如果未能解决你的问题,请参考以下文章
如何将基类的未知子类放在一个数据结构中并在 C++ 中调用重写的基类函数