Skip to content

生命周期省略规则

> 理解编译器的三条省略规则,学会判断何时需要显式标注生命周期。

生命周期省略规则详解

编译器如何推断生命周期

┌─────────────────────────────────────────────────────┐
│           三条省略规则                                │
├─────────────────────────────────────────────────────┤
│                                                     │
│  规则 1:每个引用参数获得独立的生命周期              │
│  输入生命周期推断                                    │
│                                                     │
│  fn foo(x: &str, y: &str)                           │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━          │
│  编译器推断为:                                      │
│  fn foo<'a, 'b>(x: &'a str, y: &'b str)            │
│                                                     │
│  含义:每个输入引用参数都获得一个独特的生命周期参数  │
│  避免不必要的生命周期约束                            │
│                                                     │
│  规则 2:只有一个输入生命周期时,赋值给所有输出      │
│  输出生命周期推断                                    │
│                                                     │
│  fn foo(x: &str) -> &str                            │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━          │
│  编译器推断为:                                      │
│  fn foo<'a>(x: &'a str) -> &'a str                 │
│                                                     │
│  含义:如果只有一个输入生命周期,所有输出引用       │
│  都使用同一个生命周期                                │
│                                                     │
│  规则 3:方法中 &self 或 &mut self 的生命周期        │
│  赋值给所有输出                                      │
│  方法生命周期推断                                    │
│                                                     │
│  fn foo(&self) -> &str                              │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━          │
│  编译器推断为:                                      │
│  fn foo<'a>(&'a self) -> &'a str                   │
│                                                     │
│  含义:方法的输出引用生命周期与 self 相同           │
│  这是最常见的情况                                    │
│                                                     │
└─────────────────────────────────────────────────────┘

规则应用步骤

应用顺序:

步骤 1:应用规则 1(输入生命周期)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
输入:fn foo(x: &str, y: &str) -> &str
输出:fn foo<'a, 'b>(x: &'a str, y: &'b str) -> &str
      (每个参数获得独立生命周期)

步骤 2:检查是否可应用规则 2
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
条件:只有一个输入生命周期?
• 如果是:应用规则 2,赋值给所有输出
• 如果不是:继续到步骤 3

示例(只有 1 个输入):
输入:fn foo<'a>(x: &'a str) -> &str
输出:fn foo<'a>(x: &'a str) -> &'a str
      ✅ 省略成功

步骤 3:检查是否可应用规则 3
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
条件:是方法(有 &self 或 &mut self)?
• 如果是:应用规则 3,self 生命周期赋值给输出
• 如果不是:编译器报错,需要显式标注

示例(方法):
输入:fn foo<'a>(&'a self, x: &'b str) -> &str
输出:fn foo<'a>(&'a self, x: &'b str) -> &'a str
      ✅ 省略成功(self 生命周期赋给输出)

示例(普通函数,多个输入):
输入:fn foo<'a, 'b>(x: &'a str, y: &'b str) -> &str
输出:❌ 无法推断,需要显式标注

省略成功示例

rust
// ✅ 规则 1 + 规则 2:单参数函数
fn first_word(s: &str) -> &str {
    // 编译器推断步骤:
    // 步骤 1:fn first_word<'a>(s: &'a str) -> &str
    // 步骤 2:只有一个输入,应用规则 2
    // 结果:fn first_word<'a>(s: &'a str) -> &'a str
    
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

// ✅ 规则 1:多参数,无返回引用
fn print_both(x: &str, y: &str) {
    // 编译器推断步骤:
    // 步骤 1:fn print_both<'a, 'b>(x: &'a str, y: &'b str)
    // 无输出引用,不需要规则 2 和 3
    
    println!("{} {}", x, y);
}

// ✅ 规则 3:方法
struct Data {
    value: String,
}

impl Data {
    fn get(&self) -> &str {
        // 编译器推断步骤:
        // 步骤 1:fn get<'a>(&'a self) -> &str
        // 步骤 2:有 self,应用规则 3
        // 结果:fn get<'a>(&'a self) -> &'a str
        
        &self.value
    }

    fn get_mut(&mut self) -> &mut str {
        // 规则 3 同样适用于 &mut self
        // fn get_mut<'a>(&'a mut self) -> &'a mut str
        
        &mut self.value
    }
    
    fn update(&mut self, new_value: &str) -> &str {
        // 编译器推断步骤:
        // 步骤 1:fn update<'a, 'b>(&'a mut self, new_value: &'b str) -> &str
        // 步骤 3:有 &mut self,应用规则 3
        // 结果:fn update<'a, 'b>(&'a mut self, new_value: &'b str) -> &'a str
        //       返回值的生命周期 = self 的生命周期
        
        self.value = new_value.to_string();
        &self.value
    }
}
▶ Run

省略失败案例

rust
// ❌ 需要标注:多个参数,无法确定关系
fn longest(x: &str, y: &str) -> &str {
    // 编译器推断步骤:
    // 步骤 1:fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str
    // 步骤 2:有 2 个输入生命周期,无法应用规则 2
    // 步骤 3:不是方法,无法应用规则 3
    // 结果:❌ 编译器无法推断,需要显式标注
    
    if x.len() > y.len() { x } else { y }
}

// 编译错误:
// error[E0106]: missing lifetime specifier
//  --> src/main.rs:1:23
//   |
// 1 | fn longest(x: &str, y: &str) -> &str {
//   |                     -        -  ^ 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 introducing a named lifetime parameter
//   |
// 1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
//   |           ++++     ++          ++          ++

// ✅ 正确:显式标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
▶ Run

省略规则实战判断表

函数签名规则应用是否可省略推断结果
fn f(x: &str)规则 1fn f<'a>(x: &'a str)
fn f(x: &str) -> &str规则 1 + 2fn f<'a>(x: &'a str) -> &'a str
fn f(x: &str, y: &str)规则 1fn f<'a, 'b>(x: &'a str, y: &'b str)
fn f(x: &str, y: &str) -> &str规则 1,无后续需显式标注
fn f(&self) -> &str规则 1 + 3fn f<'a>(&'a self) -> &'a str
fn f(&self, x: &str) -> &str规则 1 + 3fn f<'a, 'b>(&'a self, x: &'b str) -> &'a str
fn f(&self, x: &str, y: &str) -> &str规则 1 + 3返回 &'a str(self 的生命周期)

常见问题:混淆规则

rust
// ❌ 错误理解:认为方法中所有输入参数的生命周期都赋给输出
struct Container {
    data: String,
}

impl Container {
    fn append_and_return(&mut self, suffix: &str) -> &str {
        // 编译器推断:返回 &'self str(规则 3)
        // 不是 &'suffix str
        
        self.data.push_str(suffix);
        &self.data  // ✅ 正确:返回 self.data 的引用
    }
    
    // ❌ 如果想返回 suffix,需要显式标注
    // fn get_suffix(&self, suffix: &str) -> &str {
    //     suffix  // 编译器会推断返回 &'self str,但实际返回 suffix
    // }
    
    // ✅ 正确:显式标注返回 suffix 的生命周期
    fn get_suffix<'a>(&self, suffix: &'a str) -> &'a str {
        suffix
    }
}




---

## 小结

- 规则 1:每个引用参数获得独立的生命周期
- 规则 2:只有一个输入生命周期时,赋值给所有输出
- 规则 3:方法中 `&self` 或 `&mut self` 的生命周期赋值给输出
- 省略规则按顺序应用,无法推断时需要显式标注
- 多参数返回引用的函数通常需要显式标注

## 练习题

详见:[练习题](../../exercises/17-lifetimes)
▶ Run