bigywater.tech
Rust · 27

生命周期省略规则

你很少需要写 'a,是因为编译器有三条省略规则帮你补全。这篇把这三条规则逐条用真实代码验证,再看规则不够用时的报错,以及 '_ 这个「我知道这里有个生命周期但不想起名」的写法。

3 条
生命周期省略规则:输入各自独立 / 单一输入赋给输出 / &self 赋给输出
E0106
规则覆盖不到时的错误
'_
匿名生命周期的写法

规则 1

实测 · 本机执行输出
  源码:
      1 | fn lens(a: &str, b: &str) -> usize { a.len() + b.len() }                  // 省略写法
      2 | fn lens_explicit<'a, 'b>(a: &'a str, b: &'b str) -> usize { a.len() + b.len() }   // 等价的完整写法
      3 | fn main() {
      4 |     let a = String::from("long lived");
      5 |     let r;
      6 |     { let b = String::from("short"); r = (lens(&a, &b), lens_explicit(&a, &b)); }
      7 |     println!("两种写法结果相同:{:?}", r);
      8 | }
  编译:成功
  运行(退出码 0):
    两种写法结果相同:(15, 15)

规则 2

实测 · 本机执行输出
  源码:
      1 | fn first_char(s: &str) -> &str { &s[..1] }                 // 省略写法
      2 | fn first_char_explicit<'a>(s: &'a str) -> &'a str { &s[..1] }
      3 | fn pair(p: &(String, String)) -> (&String, &String) { (&p.0, &p.1) }   // 输出有两个引用,都借自唯一的输入
      4 | fn main() {
      5 |     let s = String::from("你好");
      6 |     println!("{} {}", first_char("abc"), first_char_explicit("xyz"));
      7 |     let t = (String::from("a"), String::from("b"));
      8 |     let (x, y) = pair(&t);
      9 |     println!("{x} {y}   {}", s.len());
     10 | }
  编译:成功
  运行(退出码 0):
    a x
    a b   6

规则 3

实测 · 本机执行输出
  源码:
      1 | struct Store { name: String }
      2 | impl Store {
      3 |     fn get(&self, other: &str) -> &str { &self.name }          // 返回值借自 self
      4 |     fn wrong(&self, other: &str) -> &str { other }              // 想返回 other:被拒绝
      5 | }
      6 | fn main() {}
  编译:失败
    warning: unused variable: `other`
     --> <实验目录>/src/u27c.rs:3:19
      |
    3 |     fn get(&self, other: &str) -> &str { &self.name }          // 返回值借自 self
      |                   ^^^^^ help: if this is intentional, prefix it with an underscore: `_other`
      |
      = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
    
    error: lifetime may not live long enough
     --> <实验目录>/src/u27c.rs:4:44
      |
    4 |     fn wrong(&self, other: &str) -> &str { other }              // 想返回 other:被拒绝
      |              -             -               ^^^^^ method was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`
      |              |             |
      |              |             let's call the lifetime of this reference `'1`
      |              let's call the lifetime of this reference `'2`
      |
    help: consider introducing a named lifetime parameter and update trait if needed
      |
    4 |     fn wrong<'a>(&self, other: &'a str) -> &'a str { other }              // 想返回 other:被拒绝
      |             ++++                ++          ++
    
    error: aborting due to 1 previous error; 1 warning emitted

方法里返回值借自 self 的更多场景见函数返回引用的生命周期。

规则不够用

实测 · 本机执行输出
  源码:
      1 | fn make() -> &str { "x" }
      2 | fn main() {}
  编译:失败
    error[E0106]: missing lifetime specifier
     --> <实验目录>/src/u27d.rs:1:14
      |
    1 | fn make() -> &str { "x" }
      |              ^ expected named lifetime parameter
      |
      = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
    help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
      |
    1 | fn make() -> &'static str { "x" }
      |               +++++++
    help: instead, you are more likely to want to return an owned value
      |
    1 - fn make() -> &str { "x" }
    1 + fn make() -> String { "x" }
      |
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0106`.
  源码:
      1 | fn pick(a: &str, b: &str) -> &str { a }
      2 | fn main() {}
  编译:失败
    error[E0106]: missing lifetime specifier
     --> <实验目录>/src/u27e.rs:1:30
      |
    1 | fn pick(a: &str, b: &str) -> &str { a }
      |            ----     ----     ^ expected named lifetime parameter
      |
      = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
    help: consider introducing a named lifetime parameter
      |
    1 | fn pick<'a>(a: &'a str, b: &'a str) -> &'a str { a }
      |        ++++     ++          ++          ++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0106`.

匿名生命周期 '_

实测 · 本机执行输出
  源码:
      1 | struct Excerpt<'a> { part: &'a str }
      2 | impl Excerpt<'_> { fn len(&self) -> usize { self.part.len() } }       // 不需要名字时用 '_
      3 | fn show(e: &Excerpt<'_>) -> usize { e.len() }
      4 | fn main() { let t = String::from("abc"); let e = Excerpt { part: &t }; println!("{}", show(&e)); }
  编译:成功
  运行(退出码 0):
    3
  编译:成功;警告:
    warning: hidden lifetime parameters in types are deprecated
     --> <实验目录>/src/u27g.rs:2:13
      |
    2 | fn show(e: &Excerpt) -> usize { e.part.len() }          // 路径里藏着一个被省略的生命周期
      |             ^^^^^^^ expected lifetime parameter
      |
      = note: requested on the command line with `-W elided-lifetimes-in-paths`
    help: indicate the anonymous lifetime
      |
    2 | fn show(e: &Excerpt<'_>) -> usize { e.part.len() }          // 路径里藏着一个被省略的生命周期
      |                    ++++
    
    warning: 1 warning emitted
  运行(退出码 0):
    3

分步演示

省略规则
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。尝试过 -Z unpretty=hir 查看展开后的签名,只显示 '_ 而没有展开成具体名字,所以这里没有使用
    • 没有验证:trait 方法与 dyn Trait 的默认对象生命周期界限、impl Trait 的捕获规则(2024 edition 变化)、高阶 trait bound(for<'a>)里的省略
    京ICP备2026067502号-1京公网安备11010502063487号