Rust 错误 E0597:borrowed value does not live long enough(存活时间不够长)
E0597 的意思是:你让一个引用(&x)比值 x 本身活得更久。 值在它所在代码块的末尾就被清理掉了,可代码块外面还有东西想用这个引用。这个错误非常常见,尤其是在内层代码块、循环和结构体里。修复通常只需要挪动一行代码,或者改为存储拥有所有权的值,而不是引用。(本页是 Rust 编译错误详解系列的一部分。)
error[E0597]: `x` does not live long enough --> src/main.rs:5:13 |4 | let x = 5; | - binding `x` declared here5 | r = &x; // r 借用了 x | ^^ borrowed value does not live long enough6 | } // x 在这里被丢弃 | - `x` dropped here while still borrowed7 | println!("r: {r}"); // ❌ r 会指向不存在的东西 | - borrow later used here第一行的意思是“x 活得不够久”。下面的标签依次是:“x 在这里声明”、“被借用的值活得不够久”、“x 在仍被借用时就被丢弃了”、“之后在这里使用了借用”。
产生这个错误的程序是这样的:
fn main() { let r; { let x = 5; r = &x; // r 借用了 x } // x 在这里被丢弃 println!("r: {r}"); // ❌ r 会指向不存在的东西}这个错误是什么意思
Section titled “这个错误是什么意思”这四个标签按顺序讲了一个完整的故事:
- 第 4 行:
x是在内层代码块{ ... }里面创建的。 - 第 5 行:
r借用了x。到这里还没问题。 - 第 6 行:代码块结束,
x被丢弃(清理掉)了。可借用还活着。 - 第 7 行:使用了
r,但它指向的值早就没了。
就像你记下了朋友住的酒店房间号。他住在那里的时候,这个号码很有用。等他退了房,纸条上的号码还在,可它通向的是一间空房,或者住着陌生人。Rust 不允许你留着一张通向空房间的纸条。
你还会看到简短的标签 “borrowed value does not live long enough”(被借用的值活得不够久)。这是同一个错误,只是标题里点出了变量名。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”引用绝不能比它指向的值活得更久。否则你就会得到一个悬垂引用:一个指向已经释放、可能已经存放了别的东西的内存的指针。在 C 和 C++ 里,这是导致崩溃和安全漏洞的经典原因(“use after free”,释放后使用)。
Rust 中的每个值,都会在它的所有者离开作用域时被丢弃,通常就是声明它的那个代码块的右花括号 }。借用检查器会比较两段范围:值活多久,以及引用用到什么时候。如果引用在值的 } 之后还被使用,你就会得到 E0597。参见作用域、借用和生命周期。
注意编译器没有抱怨什么:第 5 行借用 x 本身没有问题。问题只在第 7 行的后续使用。把最后一行删掉,程序就能编译。这是理解下面几种修法的好线索:要么让值活得更久,要么让引用的使用早点结束。
修复 1:把值移到外层作用域
Section titled “修复 1:把值移到外层作用域”当这个值确实需要和引用活得一样久时,就用这个方法。这是最常见的修法:在代码块之前(外面)声明这个值,让它至少和引用活得一样久。
fn main() { let scores = vec![72, 95, 88]; // 现在一直活到 main 结束 let best; { best = scores.iter().max().unwrap(); } println!("最高分:{best}"); // ✅ scores 还活着}输出:
最高分:95经验法则:所有者必须在借用者之前声明,并且比它活得更久。 如果你看到一个值在代码块或循环里创建,而它的引用却在外面使用,就把这个值移到外面去。
修复 2:存储拥有所有权的值,而不是引用
Section titled “修复 2:存储拥有所有权的值,而不是引用”当你要收集在循环里创建的值时,就用这个方法。循环每一轮都会创建一个新的 String,并在这一轮结束时丢弃它,所以循环外面的 Vec<&str> 无法保存指向它们的引用:
fn main() { let mut names: Vec<&str> = Vec::new(); for i in 1..=3 { let name = format!("player{i}"); names.push(&name); // ❌ `name` does not live long enough } println!("{names:?}");}修法是让 Vec 拥有这些字符串。把 Vec<&str> 改成 Vec<String>,然后 push String 本身(这会把它移动进 vector,于是它和 vector 活得一样久):
fn main() { let mut names: Vec<String> = Vec::new(); for i in 1..=3 { let name = format!("player{i}"); names.push(name); // 把 String 移动进 Vec } println!("{names:?}");}输出:
["player1", "player2", "player3"]同样的思路也适用于结构体和 HashMap:如果数据是在比容器更小的作用域里创建的,就存储 String(或 Vec<T>),而不是 &str(或 &[T])。对新手来说,拥有所有权的字段几乎总是正确的选择。参见字符串和 vector。
修复 3:克隆一份
Section titled “修复 3:克隆一份”当你还需要继续使用原来的值,或者你手里只有它的借用时,就用这个方法。持有引用的结构体(Label<'a> { text: &'a str })不能比它指向的字符串活得更久。给结构体一个拥有所有权的 String 字段,并存进一份副本:
struct Label { text: String, // 拥有所有权,而不是 &str}
fn main() { let label; { let title = String::from("Inbox"); label = Label { text: title.clone() }; println!("代码块里:{title}"); } println!("标签:{}", label.text); // ✅ label 拥有自己的副本}输出:
代码块里:Inbox标签:Inbox如果之后不再需要 title,就不用克隆,直接移动它:Label { text: title }。克隆一个短字符串开销很小,完全没问题。如果你手里只有 &str,用 .to_string() 得到一份拥有所有权的副本。
修复 4:调整代码结构,让借用及时结束
Section titled “修复 4:调整代码结构,让借用及时结束”当这个值本来就只该短暂存在时,就用这个方法。不要让值活得更久,而是趁值还活着时用掉引用。返回引用的函数经常遇到这种情况,比如经典的 longest 例子:
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { let first = String::from("Ferris the crab"); { let second = String::from("Corro"); let winner = longest(&first, &second); println!("最长的名字:{winner}"); // ✅ 在 second 被丢弃之前用掉 }}输出:
最长的名字:Ferris the crab(这里的名字保留英文,因为 len() 数的是字节,换成中文会改变比较结果。)
如果把 winner 声明在代码块外面、在代码块之后才打印,你就会因为 second 得到 E0597,即使 first 恰好更长也一样。函数签名表明结果可能借用自任意一个参数,所以两个参数都必须比结果活得更久。如果你确实需要在之后使用结果,就把它存成拥有所有权的值:let winner = longest(&first, &second).to_string();。
存到代码块外面的闭包。 用到 name 的闭包会借用它。如果闭包比 name 活得更久,你会得到 E0597,外加一个标签 value captured here(值在这里被捕获):
let say_hello;{ let name = String::from("Ferris"); say_hello = || println!("Hello, {name}!"); // ❌ `name` does not live long enough}say_hello();修复:写成 move || ...,让闭包获得 name 的所有权。参见闭包。
线程。 在 thread::spawn(|| ...) 里借用局部变量,会得到另一个错误 E0373(“closure may outlive the current function”,闭包可能比当前函数活得更久),因为线程可能比 main 运行得更久。修复:thread::spawn(move || ...)、用 Arc 共享,或者用 std::thread::scope。参见并发。
指向临时值的引用。 let label = Label { text: String::from("Inbox").as_str() }; 和 E0597 很像,但显示为 E0716,“temporary value dropped while borrowed”(临时值在借用期间被丢弃):这个 String 从来没有存进变量,所以在语句结束时就被丢弃了。修复:先把它存进一个变量(let title = String::from("Inbox");),或者把字段改成 String。
从函数返回引用。 如果值是在函数里面创建的,你得到的会是 E0515。返回拥有所有权的值即可。
- E0515:cannot return reference to local variable:同样的问题,发生在函数边界上。
- E0373:closure may outlive the current function:这个错误的线程版本,通常用
move修复。 - E0106:缺少生命周期:Rust 需要知道返回或存储的引用借用自哪里。
- E0382:borrow of moved value:相反的陷阱,在把值交出去之后还去使用它。
- E0502:可变借用与不可变借用冲突:你想修改值时,还有引用在使用它。
E0716: temporary value dropped while borrowed:和 E0597 类似,只不过针对的是从没存进变量的值。- Rust 编译错误详解
小测验
1. 在 let r; { let x = 5; r = &x; } 后面再用 println! 打印 r,真正触发 E0597 的是什么?
2. 你在每一轮循环里创建一个 String,并把 &name push 进一个 Vec<&str>。通常怎么修?
3. 线程闭包借用了一个局部 Vec。你会得到哪个错误?
得分: 0 / 3
- E0597 — Rust error codes index(英文):这个错误的官方说明。
- 借用检查器 — Rust 程序设计语言:同样的
r和x的例子,还配有一张两段生命周期的示意图。 - 借用 — 通过例子学 Rust:关于借用以及借用持续多久的简短可运行示例。
- 生命周期 — 通过例子学 Rust:编译器怎样看待每个值和每次借用的生命周期。
- E0716 — Rust error codes index(英文):关系密切的 “temporary value dropped while borrowed” 错误。