跳转到内容

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 here
5 | r = &x; // r 借用了 x
| ^^ borrowed value does not live long enough
6 | } // x 在这里被丢弃
| - `x` dropped here while still borrowed
7 | 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 会指向不存在的东西
}

这四个标签按顺序讲了一个完整的故事:

  1. 第 4 行:x 是在内层代码块 { ... } 里面创建的。
  2. 第 5 行:r 借用了 x。到这里还没问题。
  3. 第 6 行:代码块结束,x 被丢弃(清理掉)了。可借用还活着。
  4. 第 7 行:使用了 r,但它指向的值早就没了。

就像你记下了朋友住的酒店房间号。他住在那里的时候,这个号码很有用。等他退了房,纸条上的号码还在,可它通向的是一间空房,或者住着陌生人。Rust 不允许你留着一张通向空房间的纸条。

你还会看到简短的标签 “borrowed value does not live long enough”(被借用的值活得不够久)。这是同一个错误,只是标题里点出了变量名。

引用绝不能比它指向的值活得更久。否则你就会得到一个悬垂引用:一个指向已经释放、可能已经存放了别的东西的内存的指针。在 C 和 C++ 里,这是导致崩溃和安全漏洞的经典原因(“use after free”,释放后使用)。

Rust 中的每个值,都会在它的所有者离开作用域时被丢弃,通常就是声明它的那个代码块的右花括号 }。借用检查器会比较两段范围:值活多久,以及引用用到什么时候。如果引用在值的 } 之后还被使用,你就会得到 E0597。参见作用域、借用和生命周期。

注意编译器没有抱怨什么:第 5 行借用 x 本身没有问题。问题只在第 7 行的后续使用。把最后一行删掉,程序就能编译。这是理解下面几种修法的好线索:要么让值活得更久,要么让引用的使用早点结束。

当这个值确实需要和引用活得一样久时,就用这个方法。这是最常见的修法:在代码块之前(外面)声明这个值,让它至少和引用活得一样久。

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。

当你还需要继续使用原来的值,或者你手里只有它的借用时,就用这个方法。持有引用的结构体(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。返回拥有所有权的值即可。

小测验

1. 在 let r; { let x = 5; r = &x; } 后面再用 println! 打印 r,真正触发 E0597 的是什么?

2. 你在每一轮循环里创建一个 String,并把 &name push 进一个 Vec<&str>。通常怎么修?

3. 线程闭包借用了一个局部 Vec。你会得到哪个错误?

得分: 0 / 3