如何将异步函数存储在结构中并从结构实例中调用它?

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::Futures 和最新版本的 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 实际上只是将-&gt; Foo 转换为-&gt; impl Future&lt;Output = Foo&gt; 的语法糖。

我们将结构体设为泛型,并将特征绑定到匹配的泛型上。在实际代码中,您可能希望对 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

以上是关于如何将异步函数存储在结构中并从结构实例中调用它?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用异步方法从 Azure 函数中的存储读取

如何将异步任务存储在字典中并根据配置值调用它们?

如何将基类的未知子类放在一个数据结构中并在 C++ 中调用重写的基类函数

如何将 pid 的值保存在数据结构中并稍后使用 fork() 访问它?

是否可以将函数加载到一些分配的内存中并从那里运行它?

将函数存储在列表中并稍后调用它们