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

为何引入变量会使返回的Future满足Send trait?

问题描述

报错代码

async fn listen() -> std::io::Result<()> {
        HttpServer::new(|| App::new().service(hello))
        .bind(("0.0.0.0", 8888))?
        .run()
        .await
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let listen_handler = tokio::spawn(listen());
    tokio::select! {
        _ = listen_handler => {},
    }
    Ok(())
}

编译报错信息

error: future cannot be sent between threads safely
   --> src/main.rs:98:39
    |
98  |     let listen_handler = tokio::spawn(listen());
    |                                       ^^^^^^^^ future returned by `listen` is not `Send`
    |
    = help: within `impl std::future::Future<Output = Result<(), std::io::Error>>`, the trait `Send` is not implemented for `Rc<[Box<(dyn Fn() -> Pin<Box<(dyn std::future::Future<Output = Result<Box<(dyn actix_web::data::DataFactory + 'static)>, ()>> + 'static)>> + 'static)>]>` which is required by `impl std::future::Future<Output = Result<(), std::io::Error>>: Send`
note: future is not `Send` as this value is used across an await
   --> src/main.rs:92:10
    |
89  | /     HttpServer::new(|| App::new().service(hello))
90  | |         .bind(("0.0.0.0", 8888))?
    | |_________________________________- has type `ControlFlow<Result<Infallible, std::io::Error>, HttpServer<{closure@src/main.rs:89:21: 89:23}, App<actix_web::app_service::AppEntry>, actix_web::app_service::AppInit<actix_web::app_service::AppEntry, BoxBody>, BoxBody>>` which is not `Send`
91  |           .run()
92  |           .await
    |            ^^^^^ await occurs here, with the value maybe used later
note: required by a bound in `tokio::spawn`
   --> /home/liuzehao/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.42.0/src/task/spawn.rs:168:21
    |
166 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
167 |     where
168 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`

修复后可编译代码

async fn listen() -> std::io::Result<()> {
    let server = HttpServer::new(|| App::new().service(hello))
        .bind(("0.0.0.0", 8888))?
        .run();
    server.await
}

疑问

为何第二个版本可以正常编译,而第一个版本不行?


原因解析

核心差异在于异步函数生成的Future所捕获的变量是否会跨越await点,且该变量是否实现了Send trait:

  1. 第一个版本的问题:
    链式调用时,HttpServer::new().bind()?返回的HttpServer实例,编译器会判定它可能在.await之后仍被持有(逻辑上不会,但编译器的借用分析基于代码结构)。而HttpServer内部包含Rc类型(报错信息中明确提到),Rc是不实现Send的——它是非线程安全的引用计数工具,无法安全跨线程传递。这直接导致listen()返回的Future不满足Send要求,而tokio::spawn强制要求传入的Future必须是Send(因为Tokio线程池会在不同线程间调度任务),因此编译失败。

  2. 第二个版本的修复原理:
    将.run()的结果赋值给变量server后,HttpServer实例在调用.run()时就被消耗了——run()方法会拿走HttpServer的所有权,并返回一个可await的Future。此时listen()返回的Future只捕获了server这个Future,而run()返回的Future是实现了Send的,完全符合tokio::spawn的约束,因此编译通过。

简单来说:链式调用时编译器误判HttpServer会跨越await点存在,拆分变量后HttpServer在await前就被销毁,不会被Future携带,从而规避了Send检查失败的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:44:51