外观
第 6 节:async 借用陷阱——'static 要求
症状
tokio::spawn 一个用到了外面变量的 async 块,编译器拒绝:
rust
use std::time::Duration;
#[tokio::main]
async fn main() {
let data = String::from("借来的数据");
let handle = tokio::spawn(async {
tokio::time::sleep(Duration::from_millis(100)).await;
println!("{}", data); // 用了外面的 data
});
handle.await.unwrap();
}编译输出
text
error[E0373]: async block may outlive the current function, but it borrows `data`,
which is owned by the current function
--> src/main.rs:8:31
|
8 | let handle = tokio::spawn(async {
| ^^^^^ may outlive borrowed value `data`
9 | tokio::time::sleep(Duration::from_millis(100)).await;
10| println!("{}", data);
| ---- `data` is borrowed here
|
= note: async blocks are not executed immediately and must either take a
a reference or ownership of outside variables they use
help: to force the async block to take ownership of `data` (and any other
referenced variables), use the `move` keyword
|
8 | let handle = tokio::spawn(async move {
| ++++诊断
spawn 的任务必须拥有自己的数据('static)——这是 async 版本的"move 闭包"(第 15 章):任务可能比 main 活得久,借 main 的变量就是"借条比东西活得久"(编译错误篇雷区 3 的 async 版)。
看报错的关键句:
may outlive the current function:"任务可能活得比这个函数久"must either take a reference or ownership:"要么引用(不行,会过期),要么拿走所有权"- help 直接给出解药:
use the move keyword
注意:第 15 章 thread::spawn 的闭包一样要求 'static——异步任务的 'static 约束不是 async 特有,是"任务可能活得久"的通用规则。
解药
解药一:move——把数据搬进任务:
rust
let handle = tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(100)).await;
println!("{}", data); // data 的所有权搬进来了
});data 被搬进任务,main 里就不能再用了(第 3 章移动)——需要两边都用?clone 一份搬进去。
解药二:数据放共享容器(Arc)——任务和外部都要用:
rust
use std::sync::Arc;
let data = Arc::new(String::from("共享数据"));
let data_for_task = Arc::clone(&data);
let handle = tokio::spawn(async move {
println!("{}", data_for_task);
});
println!("main 里还能用:{}", data);解药三:不要 spawn,直接 join!(不需要 'static)——join! 的任务跟着当前函数走,不存在"活得比函数久":
rust
let data = String::from("数据");
let task = async {
tokio::time::sleep(Duration::from_millis(100)).await;
println!("{}", data); // join! 可以借用,不用 move!
};
tokio::join!(task);join! 和 spawn 的分工:任务不需要"独立活命"、只是并发跑 → join!(能借用,轻松);任务要"丢出去自己跑、不管生死" → spawn(必须 'static)。
预防
- 报错
may outlive→ 三选一:move搬走、Arc共享、改用join! Arc的 clone 是"发遥控器"不是复印(第 14 章):Arc::clone便宜,放心用spawn的任务函数签名:自己拥有一切——把任务需要的参数都收进函数,外面别惦记- async 函数返回
impl Future借用外部引用也会报 'static 相关错误:需要借用的异步函数,用async fn声明生命周期或改返回值(高级话题,遇到再查)