跳转到内容

Rust 错误 E0515:cannot return reference to local variable(返回局部变量的引用)

E0515 的意思是:你的函数想返回一个引用(&),而它指向的是函数自己创建的东西。 函数创建的一切都会在函数返回时被清理掉,所以这个引用会指向不存在的东西。几乎每个 Rust 新手都会遇到它,修复通常只需要去掉一个 &,直接返回值本身。(本页是 Rust 编译错误详解系列的一部分。)

error[E0515]: cannot return reference to local variable `greeting`
--> src/main.rs:3:5
|
3 | &greeting // ❌ 函数返回时 greeting 就被丢弃了
| ^^^^^^^^^ returns a reference to data owned by the current function

第一行的意思是“不能返回指向局部变量 greeting 的引用”。标签说明:这里返回的引用指向当前函数拥有的数据。

产生这个错误的程序是这样的:

fn make_greeting(name: &str) -> &str {
let greeting = format!("Hello, {name}!"); // 一个新的 String,归这个函数所有
&greeting // ❌ 函数返回时 greeting 就被丢弃了
}
fn main() {
let text = make_greeting("Ferris");
println!("{text}");
}

同一个错误有几种不同的措辞,意思都一样:

  • 写 &greeting 时,是 cannot return reference to local variable(不能返回局部变量的引用)。
  • 调用会借用的方法时,比如 greeting.as_str() 或 &scores[2..],是 cannot return value referencing local variable(不能返回引用了局部变量的值)。
  • 数据从来没有存进变量时,比如 text.to_uppercase().as_str(),是 cannot return value referencing temporary value(不能返回引用了临时值的值)。

每个版本都带着这个标签:“returns a value referencing data owned by the current function”(返回的值引用了当前函数拥有的数据)。

greeting 是一个局部变量:它在 make_greeting 里创建,归这个函数所有。函数返回时,它所有的局部变量都会被丢弃。你返回的 &str 会指向一个已经不存在的 String。

这就像你搬出了一套公寓,却把钥匙交给朋友。钥匙是真的,可公寓已经空了,或者住进了别人。朋友需要的是家具,而不是钥匙。

一种常见的掉坑路线是:你先在一个返回 &str 的函数里直接返回了 greeting(一个 String),得到了 E0308(类型不匹配),编译器建议 “consider borrowing here: &greeting”(考虑在这里借用)。这个建议修好了类型,却直接把你带进了 E0515。真正的修法是改掉返回类型。

引用绝不能比它指向的值活得更久。返回指向局部变量的引用,会制造出一个悬垂引用,也就是指向已释放内存的指针。在 C 和 C++ 里,这样的代码能编译,然后崩溃(更糟的是,悄悄返回一堆垃圾数据)。Rust 在编译时就把它拦下来。

返回的引用必须借用自函数外面活着的东西:调用方传进来的参数、self 的某个字段,或者像字符串字面量这样的 'static 值。函数自己的局部变量不行。这也是为什么生命周期注解修不好 E0515:'a 只是描述数据活多久,并不能让局部变量活得更久。参见所有权和借用。

当函数创建了这份数据时,就用这个方法。大多数情况下这都是正确答案。返回 String 而不是 &str(或者返回 Vec<T> 而不是 &[T]),并去掉 &。值会被移动给调用方,由调用方拥有:

fn make_greeting(name: &str) -> String {
let greeting = format!("你好,{name}!");
greeting // ✅ 把整个 String 交给调用方
}
fn top_scores() -> Vec<u32> {
let mut scores = vec![42, 97, 63, 88];
scores.sort();
scores.split_off(2) // 最高的两个,作为一个新的 Vec
}
fn main() {
let text = make_greeting("Ferris");
println!("{text}");
println!("{:?}", top_scores());
}

输出:

你好,Ferris!
[88, 97]
▶ 动手试一试

不用担心开销:返回 String 或 Vec 只会移动一个小小的指针、长度和容量,文本本身并不会被复制。你也可以直接把 format!("你好,{name}!") 写成最后一行。

修复 2:把数据作为参数传进来,返回指向它的引用

Section titled “修复 2:把数据作为参数传进来,返回指向它的引用”

当调用方已经拥有这份数据、你只想指向其中一部分时,就用这个方法。如果函数处理的是一个借用的参数,返回的引用就借用自这个参数,而它活在函数外面:

fn top_two(scores: &mut [u32]) -> &[u32] {
scores.sort();
&scores[scores.len() - 2..] // ✅ 指向调用方的数据
}
fn main() {
let mut scores = vec![42, 97, 63, 88]; // 归 main 所有
let best = top_two(&mut scores);
println!("{best:?}");
}

输出:

[88, 97]
▶ 动手试一试

只有一个引用参数时,Rust 会帮你补上生命周期。有两个或更多时,你可能需要写 'a(见 E0106)。这个方法只在你返回输入的一部分时才管用。从输入新建出来的值(to_uppercase()、format!、trim().to_string())又是一个局部变量,那就回到修复 1。参见 slice。

修复 3:字符串字面量就返回 &'static str

Section titled “修复 3:字符串字面量就返回 &'static str”

当所有可能的结果都是写死在代码里的固定文本时,就用这个方法。字符串字面量存放在程序本身里,永远活着,所以返回指向它的引用总是安全的:

fn status_text(code: u16) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
500 => "Internal Server Error",
_ => "Unknown",
}
}
fn main() {
for code in [200, 404, 418] {
println!("{code}: {}", status_text(code));
}
}

输出:

200: OK
404: Not Found
418: Unknown
▶ 动手试一试

别想着在签名里写上 &'static str 来“修好” format! 的结果。你只会再次得到 E0515,因为那个 String 仍然是局部的。

修复 4:有时借用、有时新建,就用 Cow

Section titled “修复 4:有时借用、有时新建,就用 Cow”

当函数通常原样返回输入,但有时又得新建一个字符串时,就用这个方法。Cow(clone-on-write,写时克隆)既可以装一个借用的 &str,也可以装一个拥有所有权的 String,所以只在必要时才分配内存:

use std::borrow::Cow;
fn safe_file_name(name: &str) -> Cow<'_, str> {
if name.contains(' ') {
Cow::Owned(name.replace(' ', "_")) // 只在需要时才新建 String
} else {
Cow::Borrowed(name) // 完全不复制
}
}
fn main() {
for name in ["report.txt", "my notes.txt"] {
let clean = safe_file_name(name);
let kind = match clean {
Cow::Borrowed(_) => "借用",
Cow::Owned(_) => "新建",
};
println!("{clean}({kind})");
}
}

输出:

report.txt(借用)
my_notes.txt(新建)
▶ 动手试一试

Cow 是一种优化。如果不确定是否需要它,就返回 String(修复 1)。更多内容见 Weak 与 Cow。

返回局部 String 的 s.as_str()。 greeting.as_str() 会借用 greeting,所以它和 &greeting 一样会失败,措辞是 “cannot return value referencing local variable”。修复:返回 greeting,并把返回类型改成 String。

会新建字符串的 .map()。 下面这段看起来没什么问题,但每次 to_lowercase() 都会创建一个临时 String,它在 .as_str() 之后马上就被丢弃了:

fn tags(input: &str) -> Vec<&str> {
input.split(',').map(|t| t.trim().to_lowercase().as_str()).collect()
// ❌ cannot return value referencing temporary value
}

修复:返回 Vec<String>,并去掉 .as_str():.map(|t| t.trim().to_lowercase()).collect()。(如果没有 to_lowercase(),t.trim() 是 input 的一个 slice,那么 Vec<&str> 就没问题。参见迭代器。)

格式化字段的方法。 fn full_name(&self) -> &str { &format!("{} {}", self.first, self.last) } 会报 “cannot return reference to temporary value”(不能返回指向临时值的引用)。修复:-> String。返回 &self.first 没问题,因为这份数据存在结构体里,而不是方法里。参见结构体。

没有引用参数的函数。 fn make_greeting() -> &String 会更早失败,报的是 E0106,因为它没有任何可以借用的东西。修法一样:返回 String。

小测验

1. 一个函数用 format! 拼出一条消息,然后返回 &message。通常正确的修法是什么?

2. 下面哪个函数能编译?

3. 什么时候适合用 Cow<str> 作为返回类型?

得分: 0 / 3