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

