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

Nic*_*all 10 struct asynchronous future rust async-await

我正在尝试使用新的async/await语法、std::future::Futures 和 Tokio 的最新版本来实现这一点。我正在使用 Tokio0.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(())
}
Run Code Online (Sandbox Code Playgroud)

当然,此代码无法编译并出现以下错误:

error[E0412]: cannot find type `StorableAsyncFn` in this scope
Run Code Online (Sandbox Code Playgroud)

StorableAsyncFn 此处未定义,这是我要定义的类型。

Ibr*_*med 18

存储异步函数的另一种方法是使用特征对象。如果您希望能够在运行时动态交换函数,或者存储异步函数的集合,这非常有用。为此,我们可以存储一个Fn返回 boxed 的boxed Future

use futures::future::BoxFuture; // Pin<Box<dyn Future<Output = T> + Send>>

struct S {
    foo: Box<dyn Fn(u8) -> BoxFuture<'static, u8>,
}
Run Code Online (Sandbox Code Playgroud)

然而,如果我们尝试初始化S,我们立即遇到一个问题:

async fn foo(x: u8) -> u8 {
    x * 2
}

let s = S { foo: Box::new(foo) };
Run Code Online (Sandbox Code Playgroud)
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`
Run Code Online (Sandbox Code Playgroud)

错误信息非常清楚。S期望拥有一个Future,但async函数返回impl Future。我们需要更新函数签名以匹配存储的特征对象:

fn foo(x: u8) -> BoxFuture<'static, u8> {
    Box::pin(async { x * 2 })
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这工作得很好,但我们可以通过编写自定义特征并自动执行转换来使其变得更好:

trait AsyncFn {
    fn call(&self, args: u8) -> BoxFuture<'static, u8>;
}
Run Code Online (Sandbox Code Playgroud)

并为我们要存储的函数类型实现它:

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))
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以存储自定义特征的特征对象!

struct S {
    foo: Box<dyn AsyncFn>,
}


let s = S { foo: Box::new(foo) };
s.foo.call(12).await // => 24
Run Code Online (Sandbox Code Playgroud)


She*_*ter 14

让我们将其用作最小的、可重现的示例

async fn foo(x: u8) -> u8 {
    2 * x
}

struct S {
    foo: (),
}

async fn example() {
    let s = S { foo };
}
Run Code Online (Sandbox Code Playgroud)

它产生错误:

async fn foo(x: u8) -> u8 {
    2 * x
}

struct S {
    foo: (),
}

async fn example() {
    let s = S { foo };
}
Run Code Online (Sandbox Code Playgroud)

的类型foo是一个函数指针,它接受 au8并返回一些实现 trait 的类型std::future::Futureasync 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;
}
Run Code Online (Sandbox Code Playgroud)

为了更加灵活,您可以使用另一个泛型来存储闭包,而不是仅强制使用函数指针:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

也可以看看: