You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何使用Tokio spawn时,我的Fn需要满足Sync约束?

为何MyFn必须添加Sync约束?

以下代码触发编译器错误,提示MyFn应添加Sync约束:

use std::future::Future;

fn spawn<MyFn, Out>(func: MyFn)
where
    Out: Future + Send + 'static,
    MyFn: Fn() -> Out + Send + 'static,
{
    tokio::spawn(call_func(func));
}

async fn call_func<MyFn, Out>(func: MyFn)
where
    MyFn: Fn() -> Out,
    Out: Future,
{
    func().await;
    func().await;
}

编译时出现如下错误:

Compiling playground v0.0.1 (/playground)
error: future cannot be sent between threads safely
   --> src/main.rs:8:18
    |
8   |     tokio::spawn(call_func(func));
    |                  ^^^^^^^^^^^^^^^ future returned by `call_func` is not `Send`
    |
note: future is not `Send` as this value is used across an await
   --> src/main.rs:16:11
    |
16  |     func().await;
    |     ----  ^^^^^^ await occurs here, with `func` maybe used later
    |     |
    |     has type `&MyFn` which is not `Send`
note: `func` is later dropped here
   --> src/main.rs:16:17
    |
16  |     func().await;
    |                 ^
help: consider moving this into a `let` binding to create a shorter lived borrow
   --> src/main.rs:16:5
    |
16  |     func().await;
    |     ^^^^^^
note: required by a bound in `tokio::spawn`
   --> /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.21.2/src/task/spawn.rs:127:21
    |
127 |         T: Future + Send + 'static,
    |                     ^^^^ required by this bound in `tokio::spawn`
help: consider dereferencing here
    |
8   |     tokio::spawn(*call_func(func));
    |                  +
help: consider further restricting this bound
    |
6   |     MyFn: Fn() -> Out + Send + 'static + std::marker::Sync,
    |                                        +++++++++++++++++++

error: could not compile `playground` due to previous error

根据tokio::spawn的定义,它仅要求:

pub fn spawn<T>(future: T) -> JoinHandle<T::Output>ⓘ
where
    T: Future + Send + 'static,
    T::Output: Send + 'static,

原因分析

核心逻辑在于call_func返回的Future的Send属性依赖MyFn的Sync标记:

  1. 在call_func中,func被两次调用并await,编译器生成的Future会在第一个await点保留func的引用——因为后续还要再次调用它,这个引用会跨await点存在。
  2. tokio::spawn要求提交的Future必须是Send的,这意味着Future中所有跨await点持有的数据都得是Send的。
  3. 这里跨await点持有的是&MyFn,而引用类型&T要具备Send性,前提是T实现Sync(Sync表示T可以被多个线程安全地共享引用)。
  4. 因此,要让call_func的返回Future满足Send,必须给MyFn添加Sync约束,确保&MyFn是Send的,最终让整个Future符合tokio::spawn的要求。

另外还有一种替代方案:通过let绑定提前获取Future,缩短func的引用生命周期,这样就不需要Sync约束:

async fn call_func<MyFn, Out>(func: MyFn)
where
    MyFn: Fn() -> Out,
    Out: Future,
{
    let fut1 = func();
    fut1.await;
    let fut2 = func();
    fut2.await;
}

这种写法里,func的引用不会跨await点存在,编译器也就不会要求MyFn实现Sync。


内容的提问来源于stack exchange,提问作者Nick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 16:45:42