【问题标题】:How can I store an async function in a struct and call it from a struct instance?如何将异步函数存储在结构中并从结构实例中调用它?
【发布时间】:2026-02-10 04:30:02
【问题描述】:

我正在尝试使用新的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 此处未定义,这是我要定义的类型。

【问题讨论】:

    标签: asynchronous struct rust async-await future


    【解决方案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;
        }
    }
    

    另见:

    【讨论】:

    • 感谢您的超清晰回答。另外,很高兴知道您可以将通用参数设为Future,而无需指定Output