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”(返回的值引用了当前函数拥有的数据)。
这个错误是什么意思
Section titled “这个错误是什么意思”greeting 是一个局部变量:它在 make_greeting 里创建,归这个函数所有。函数返回时,它所有的局部变量都会被丢弃。你返回的 &str 会指向一个已经不存在的 String。
这就像你搬出了一套公寓,却把钥匙交给朋友。钥匙是真的,可公寓已经空了,或者住进了别人。朋友需要的是家具,而不是钥匙。
一种常见的掉坑路线是:你先在一个返回 &str 的函数里直接返回了 greeting(一个 String),得到了 E0308(类型不匹配),编译器建议 “consider borrowing here: &greeting”(考虑在这里借用)。这个建议修好了类型,却直接把你带进了 E0515。真正的修法是改掉返回类型。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”引用绝不能比它指向的值活得更久。返回指向局部变量的引用,会制造出一个悬垂引用,也就是指向已释放内存的指针。在 C 和 C++ 里,这样的代码能编译,然后崩溃(更糟的是,悄悄返回一堆垃圾数据)。Rust 在编译时就把它拦下来。
返回的引用必须借用自函数外面活着的东西:调用方传进来的参数、self 的某个字段,或者像字符串字面量这样的 'static 值。函数自己的局部变量不行。这也是为什么生命周期注解修不好 E0515:'a 只是描述数据活多久,并不能让局部变量活得更久。参见所有权和借用。
修复 1:返回拥有所有权的值
Section titled “修复 1:返回拥有所有权的值”当函数创建了这份数据时,就用这个方法。大多数情况下这都是正确答案。返回 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: OK404: Not Found418: 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。
- E0106:缺少生命周期:经常最先出现;加上
'a也修不好指向局部变量的引用。 - E0597:borrowed value does not live long enough:同样的问题,发生在一个函数内部,起因是代码块或循环,而不是 return。
- E0308:类型不匹配:声明返回
&str,却返回了String。要改返回类型,而不是加&。 - E0382:borrow of moved value:改用拥有所有权的值之后,要当心在值被移动之后还去使用它。
E0716: temporary value dropped while borrowed:指向临时值的引用,在同一个函数里稍后又被使用。- Rust 编译错误详解
小测验
1. 一个函数用 format! 拼出一条消息,然后返回 &message。通常正确的修法是什么?
2. 下面哪个函数能编译?
3. 什么时候适合用 Cow<str> 作为返回类型?
得分: 0 / 3
- E0515 — Rust error codes index(英文):这个错误的官方说明。
- 悬垂引用 — Rust 程序设计语言:
dangle的例子,以及为什么答案是直接返回String。 - 生命周期确保引用有效 — Rust 程序设计语言:为什么返回的引用必须和某个参数关联起来。
- Slice 类型 — Rust 程序设计语言:返回借用自输入的
&strslice,也就是修复 2 的安全写法。 Cow— standard library docs(英文):修复 4 中的写时克隆类型,附有示例。