bigywater.tech
Rust · 24

函数返回引用的生命周期

「函数能不能返回引用」是写 Rust 时最常撞上的编译错误之一。这篇把五种典型情形逐个编译:什么时候自动成立、返回值究竟借自哪个参数、哪些写法一定不行,以及为什么字面量永远可以。

E0515
返回指向局部变量或按值参数的引用
借自 self
有 &self 时返回值默认借自 self
'static
字面量与 static 变量可以安全返回

单个输入引用

实测 · 本机执行输出
  源码:
      1 | fn first_word(s: &str) -> &str { s.split(' ').next().unwrap_or("") }
      2 | fn main() {
      3 |     let text = String::from("hello world");
      4 |     let w = first_word(&text);
      5 |     println!("first_word = {w}");
      6 |     drop(text);               // text 被释放之后还用 w 吗?
      7 | }
  编译:成功
  运行(退出码 0):
    first_word = hello
  编译:失败
    error[E0505]: cannot move out of `text` because it is borrowed
     --> <实验目录>/src/u24b.rs:5:10
      |
    3 |     let text = String::from("hello world");
      |         ---- binding `text` declared here
    4 |     let w = first_word(&text);
      |                        ----- borrow of `text` occurs here
    5 |     drop(text);
      |          ^^^^ move out of `text` occurs here
    6 |     println!("{w}");
      |                - borrow later used here
      |
    help: consider cloning the value if the performance cost is acceptable
      |
    4 |     let w = first_word(&text.clone());
      |                             ++++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0505`.

&self 与其它参数

实测 · 本机执行输出
  源码:
      1 | struct Config { name: String }
      2 | impl Config {
      3 |     fn name_or<'a>(&'a self, fallback: &str) -> &'a str { if self.name.is_empty() { "(空)" } else { &self.name } }
      4 |     fn pick(&self, _other: &str) -> &str { &self.name }       // 省略规则:返回值借自 &self
      5 | }
      6 | fn main() {
      7 |     let c = Config { name: "prod".into() };
      8 |     let n;
      9 |     {
     10 |         let tmp = String::from("临时参数");
     11 |         n = c.pick(&tmp);          // tmp 比 n 先死,不影响 n:n 借自 c
     12 |     }
     13 |     println!("n = {n}");
     14 | }
  编译:成功
  运行(退出码 0):
    n = prod

不能返回局部数据的引用

实测 · 本机执行输出
  源码:
      1 | fn make<'a>() -> &'a str {
      2 |     let s = String::from("本地");
      3 |     s.as_str()
      4 | }
      5 | fn main() { println!("{}", make()); }
  编译:失败
    error[E0515]: cannot return value referencing local variable `s`
     --> <实验目录>/src/u24d.rs:3:5
      |
    3 |     s.as_str()
      |     -^^^^^^^^^
      |     |
      |     returns a value referencing data owned by the current function
      |     `s` is borrowed here
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0515`.
  源码:
      1 | fn consume_and_view(s: String) -> &'static str { &s }
      2 | fn main() { println!("{}", consume_and_view("x".into())); }
  编译:失败
    error[E0515]: cannot return reference to function parameter `s`
     --> <实验目录>/src/u24e.rs:1:50
      |
    1 | fn consume_and_view(s: String) -> &'static str { &s }
      |                                                  ^^ returns a reference to data owned by the current function
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0515`.

借自集合

实测 · 本机执行输出
  源码:
      1 | fn find<'a>(v: &'a [String], key: &str) -> Option<&'a String> { v.iter().find(|s| s.as_str() == key) }
      2 | fn main() {
      3 |     let names = vec!["ann".to_string(), "bob".to_string()];
      4 |     let found;
      5 |     {
      6 |         let key = String::from("bob");         // key 在内层块里,先死
      7 |         found = find(&names, &key);
      8 |     }
      9 |     println!("{:?}", found);                    // found 借自 names,没问题
     10 | }
  编译:成功
  运行(退出码 0):
    Some("bob")
  源码:
      1 | fn find<'a>(v: &'a [String], key: &str) -> Option<&'a String> { v.iter().find(|s| s.as_str() == key) }
      2 | fn main() {
      3 |     let found;
      4 |     {
      5 |         let names = vec!["ann".to_string(), "bob".to_string()];
      6 |         found = find(&names, "bob");
      7 |     }
      8 |     println!("{:?}", found);
      9 | }
  编译:失败
    error[E0597]: `names` does not live long enough
     --> <实验目录>/src/u24g.rs:6:22
      |
    5 |         let names = vec!["ann".to_string(), "bob".to_string()];
      |             ----- binding `names` declared here
    6 |         found = find(&names, "bob");
      |                      ^^^^^^ borrowed value does not live long enough
    7 |     }
      |     - `names` dropped here while still borrowed
    8 |     println!("{:?}", found);
      |                      ----- borrow later used here
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0597`.

返回 &'static

实测 · 本机执行输出
  源码:
      1 | static NAME: &str = "全局名字";
      2 | fn greeting(formal: bool) -> &'static str { if formal { "您好" } else { "嗨" } }
      3 | fn name() -> &'static str { NAME }
      4 | fn main() {
      5 |     let g = { let flag = true; greeting(flag) };     // flag 已经出作用域,g 仍然有效
      6 |     println!("{g} {}", name());
      7 | }
  编译:成功
  运行(退出码 0):
    您好 全局名字

生命周期标注的含义见生命周期标注到底描述什么,省略规则见生命周期省略规则。

分步演示

函数返回引用
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。错误信息为原样的 rustc 1.98.1 输出
    • 没有验证:返回 impl Trait + 'a、返回 Box、迭代器适配器的返回生命周期、async fn 返回借用
    京ICP备2026067502号-1京公网安备11010502063487号