Rust 错误 E0716:temporary value dropped while borrowed(临时值在借用期间被丢弃)
E0716 的意思是:你从一个没有变量名的值那里借用了数据,然后在后面的代码行里还在用这个借用。 Rust 会在语句结束时扔掉这种没有名字的值,于是那个借用就指向了空无。它看起来很吓人,其实是最容易修的借用错误之一:通常只要把一行拆成两行就好。(这一页属于 Rust 编译错误详解系列。)
error[E0716]: temporary value dropped while borrowed --> src/main.rs:2:13 |2 | let s = String::from("hi").as_str(); // s 借用了一个没有名字的 String | ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use3 | println!("{s}"); // ❌ 那个 String 已经没了 | - borrow later used here |help: consider using a `let` binding to create a longer lived value |2 ~ let binding = String::from("hi");3 ~ let s = binding.as_str(); // s 借用了一个没有名字的 String |第一行的意思是“临时值在借用期间被丢弃了”。help 建议用一个 let 绑定,创建一个活得更久的值。
产生这个错误的程序是这样的:
fn main() { let s = String::from("hi").as_str(); // s 借用了一个没有名字的 String println!("{s}"); // ❌ 那个 String 已经没了}这个错误是什么意思
Section titled “这个错误是什么意思”临时值(temporary)是在表达式中间创建、却从来没有存进变量的值。在上面那一行里,String::from("hi") 确实创建了一个 String,但没有任何东西拥有它:没有为它写 let 名字 = ...。Rust 会在语句结束时,也就是 ; 的位置,丢弃(清理)临时值。
从左到右读这三个标签:
String::from("hi")下面的^^^^:这里创建了一个临时的String。- 行尾的
-:在这里,也就是;处,那个String被释放了(temporary value is freed at the end of this statement)。 - 第 3 行
s下面的-:可是s(一个指向那个String内部的&str)在这里还在被使用(borrow later used here)。
想象一位服务员把你点的菜写在便利贴上,一转身就把便利贴扔了。如果你告诉朋友“菜单写在那张便利贴上”,朋友什么也找不到。你得自己留着那张便利贴,或者把点的菜记在一个不会丢的地方。
信息里的 help: 部分通常就是答案:创建一个变量(rustc 把它叫作 binding),让这个值有一个活得更久的所有者。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”这和 E0597 是同一条规则:引用绝不能比它指向的值活得更久,否则它就会变成指向已释放内存的悬垂引用。唯一的区别是:E0597 里的值有名字,在 } 处死去;E0716 里的值没有名字,在 ; 处死去。
在同一条语句里使用临时值完全没问题。println!("{}", String::from("hi").as_str()); 可以编译,因为借用在 ; 之前就结束了。只有当后面的代码行还需要这个借用时,才会出错。参见借用什么时候结束?和生命周期。
有一个很有用的例外,叫作临时值生命周期延长(temporary lifetime extension)。如果你在 let 里直接在临时值前面写 &(或 &mut),Rust 会悄悄让这个临时值和变量活得一样久。所以下面这几行都能编译:
struct Label<'a> { text: &'a str,}
fn make_title() -> String { String::from("收件箱")}
fn main() { let greeting = &String::from("你好"); // ✅ 延长了:& 直接写在 let 里 let queue = &mut vec![1, 2, 3]; // ✅ 延长了 queue.push(4); let label = Label { text: &make_title() }; // ✅ 延长了:& 直接写在结构体字面量里 println!("{greeting} {queue:?} {}", label.text);}输出:
你好 [1, 2, 3, 4] 收件箱这种延长不会穿过方法调用或函数调用。String::from("hi").as_str()、make_title().as_str()、vec![1, 2].iter() 和 shout(&make_title()) 都是在调用内部借用临时值,所以临时值照样在 ; 处死去,你会得到 E0716。不用去背所有情况:看到 E0716,就给这个值起个名字。
修复 1:用 let 给临时值起个名字
Section titled “修复 1:用 let 给临时值起个名字”几乎每次都选这个方法。这是编译器给的建议,也是大多数 E0716 的正确答案。先把值存进它自己的变量,再在下一行从这个变量借用。
一个很常见的例子是拆分一行输入。read_command() 返回一个新的 String,.trim() 和 .split(' ') 都从它那里借用,而 Vec<&str> 保存着这些借用:
let parts: Vec<&str> = read_command().trim().split(' ').collect(); // ❌ E0716Rust 会指着 read_command() 说,它在语句结束时就被释放了。给它起个名字:
fn read_command() -> String { // 真正的程序里,这里会从 stdin 读一行。 String::from(" add milk eggs \n")}
fn main() { let line = read_command(); // 现在这个 String 一直活到 main 结束 let parts: Vec<&str> = line.trim().split(' ').collect(); println!("{parts:?}"); println!("命令:{},物品数:{}", parts[0], parts.len() - 1);}输出:
["add", "milk", "eggs"]命令:add,物品数:2处理真实输入时你也会碰到一模一样的情况:对 io::stdin().lines() 写 lines.next().unwrap().unwrap().trim().split(' ').collect(),会因为同样的原因报 E0716。先单独写一行 let line = lines.next().unwrap().unwrap();。参见字符串。
修复 2:存拥有所有权的值,而不是引用
Section titled “修复 2:存拥有所有权的值,而不是引用”当借用需要活得比你能创建的任何变量都久时,选这个方法,比如放在一个要到处传递的结构体里,或者放进一个在循环里不断填充的 Vec。循环里的 let 每一轮结束都会被丢弃,那样只会把 E0716 变成 E0597。
下面是两个出错的版本:一个结构体保存了来自临时值的 &str,一个循环把新字符串的借用放进 Vec:
let label = Label { text: make_title().as_str() }; // ❌ E0716
let mut names: Vec<&str> = Vec::new();for name in ["ferris", "corro"] { names.push(name.to_uppercase().as_str()); // ❌ E0716}修法是让结构体和 Vec 拥有它们的字符串。用 String 代替 &str,并去掉 .as_str():
struct Label { text: String, // 拥有所有权,所以不需要生命周期}
fn make_title() -> String { String::from("收件箱")}
fn main() { let label = Label { text: make_title() }; // 这个 String 被移动进了 Label println!("标签:{}", label.text);
let mut names: Vec<String> = Vec::new(); for name in ["ferris", "corro"] { names.push(name.to_uppercase()); // Vec 拥有每一个新的 String } println!("{names:?}");}输出:
标签:收件箱["FERRIS", "CORRO"]对新手来说,拥有所有权的字段(String、Vec<T>)几乎总是最省事的选择。带 &str 字段的结构体需要生命周期,而且永远不能比它借用的数据活得更久。参见持有引用的结构体和结构体。
修复 3:调整调用链
Section titled “修复 3:调整调用链”当临时值是一条迭代器链的开头时,选这个方法。下面这两行用 vec![...] 创建了一个 Vec,用 .iter() 借用它,然后把迭代器(或它的结果)留到后面用:
let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716let best = vec![72, 95, 88].iter().max(); // ❌ E0716前面的 &mut 没有用:生命周期延长让迭代器活下来了,却没有让 .iter() 借用的那个 Vec 活下来。有两条出路:
- 用
into_iter(),让迭代器拥有这个Vec(rustc 也会这样建议)。这样也不需要&mut了,写let mut就行。 - 用一个拥有所有权的结果结束这条链,比如
.copied()、.cloned()、.sum(),或者对普通数字用.collect::<Vec<_>>(),这样过了;之后就没有任何东西还借用着临时值。
fn main() { // 迭代器拥有这个 Vec,所以没有借用任何临时值 let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10); println!("{:?}", doubled.next()); println!("{:?}", doubled.next());
// .copied() 把 Option<&i32> 变成 Option<i32>,一个不带借用的值 let best = vec![72, 95, 88].iter().max().copied(); println!("最高分:{best:?}");}输出:
Some(10)Some(20)最高分:Some(95)如果之后还要再用这个 Vec,就回到修复 1:先写 let scores = vec![72, 95, 88];,再用 scores.iter().max()。参见迭代器。
在调用链中间给 Mutex 加锁。 let first = names.lock().unwrap().first(); 会报 E0716,因为锁守卫(lock guard)是一个临时值。它被丢弃时锁就释放了,所以 first 不能继续指向锁里面的数据。修法:先写 let guard = names.lock().unwrap();,再调用 guard.first();或者用 .first().cloned() 一次性把数据复制出来。参见共享状态。
把 &临时值 传给一个返回引用的函数。 对于 fn shout(s: &str) -> &str,let t = shout(&make_title()); 这一行会报 E0716:返回的 &str 借用了那个临时的 String,而生命周期延长穿不过函数调用。修法:先写 let title = make_title();,再调用 shout(&title)。
从函数里返回它。 如果你把 make_title().as_str() 写在一个返回引用(比如 -> &'static str)的函数的最后一行,你得到的会是 E0515(“cannot return value referencing temporary value”,不能返回引用了临时值的值)。修法:直接返回这个 String。
- E0597:borrowed value does not live long enough:同样的问题,只不过这个值有名字,在
}处被丢弃。 - E0515:cannot return reference to local variable:从函数里返回对局部值或临时值的借用。
- E0106:missing lifetime specifier:
text: &str这样的结构体字段需要生命周期。这往往说明用String更合适。 - E0502:mutable borrow while immutably borrowed:另一个“借用还在使用中”的错误,不过它关心的是修改数据,而不是丢弃数据。
- E0382:borrow of moved value:换成拥有所有权的值之后,要注意别在移动之后再使用这个值。
- Rust 编译错误详解
小测验
1. 在 Rust 里,“临时值”(temporary)是什么?
2. let s = String::from("hi").as_str(); 后面跟着 println!("{s}");,报了 E0716。通常怎么修?
3. 多亏了临时值生命周期延长,下面哪一行能编译?
得分: 0 / 3
- E0716 — Rust 错误代码索引(英文):这个错误的官方说明,附
let修法。 - 引用与借用 — Rust 程序设计语言:借用规则,包括为什么引用永远不会悬垂。
- Temporary scopes — The Rust Reference(英文):临时值什么时候被丢弃的精确规则。
- Temporary lifetime extension — The Rust Reference(英文):哪些
let写法能让临时值活下来,附示例。 - 生命周期确保引用有效 — Rust 程序设计语言:Rust 怎样比较值活多久和引用用多久。