# 生命周期省略与标注

Source: https://codewiki.com/zh/rust/lifetime-annotations/

> - **what**: 生命周期标注为引用的有效区间命名，用来表达输入引用、输出引用和借用字段之间的关系； 它是类型契约，不会让任何值活得更久。
> - **trap**: 给多个引用写同一个 `'a` 会建立约束，却不代表它们来自相同词法作用域； 滥加 `'static` 也不能修复局部引用。
> - **fix**: 先追踪每个返回引用来自哪个输入，只标注真正的依赖； 能由省略规则唯一确定时就省略，数据必须独立存在时则返回拥有型值。

## 是什么，为什么存在

生命周期（lifetime）是引用可以被安全使用的程序区域。
每条引用都有生命周期，但大多数生命周期由编译器推断，不需要写在源码中。
它描述的是引用的有效性，不等同于变量可见的整个词法作用域。

生命周期标注（lifetime annotation）以撇号开头，例如 `'a`、`'input` 和 `'static`。
它像类型参数一样在泛型参数列表中声明，但代表区域关系，而不是一种运行时类型。
标注只出现在类型与签名中，编译后不需要保存一份生命周期值。

当函数接收多个引用并返回引用时，函数体可能从不止一个输入借出数据。
仅凭 `fn select(x: &str, y: &str) -> &str`，调用方看不出结果依赖 `x`、`y` 还是两者。
显式参数把这种来源关系变成可检查的 API 契约。

例如，`fn select<'a>(x: &'a str, y: &'a str) -> &'a str` 表示调用方要选择一个区域 `'a`，两个输入在该区域内都有效，结果也只能在该区域内使用。
它不要求两个所有者在同一个代码块创建或销毁；较长的借用可以缩短到共同可用的区域。
函数实现必须对每一种满足契约的调用都安全。

借用检查器（borrow checker）结合签名、控制流与引用的实际使用位置求解这些约束。
若结果可能越过来源数据的有效区间，它会在编译时拒绝代码。
生命周期标注给分析增加关系，却不会改变所有者的销毁位置。

你会在返回切片的函数、保存引用的结构体、闭包和 trait 对象、线程边界以及泛型 trait 约束中遇到这套语法。
普通只读参数往往可以依赖省略规则；跨越函数边界保存或返回借用时，关系才更常需要显式表达。

## 工作原理

### 签名是一组约束

生命周期参数在函数名后的尖括号中声明，再写到 `&` 与被引用类型之间。
`&'a T` 是生命周期为 `'a` 的共享引用，`&'a mut T` 是同一区域内的可变引用。
描述性名称如 `'source` 适合复杂签名，简短关系通常使用 `'a` 和 `'b`。

输出上的 `'a` 必须与某个有效来源建立联系。
编译器不会根据函数返回时碰巧执行的分支，为公开签名推导条件式关系。
如果实现可能返回两个输入之一，就要给两个输入与输出提供共同约束。

同名参数表达的是最低保证，而不是强制实际借用拥有完全相同的长度。
在调用处，编译器可以把较长引用重新借用为更短引用，并选择满足全部位置的区域。
因此，“同一个 `'a`”应读成“在同一个所选区域内有效”。

不同名称表示签名没有自动建立关系。
若 `fn prefix<'text>(text: &'text str, delimiter: &str) -> &'text str` 只从 `text` 返回切片，分隔符的借用就不应限制结果。
精确签名既扩大合法调用范围，也让审查者看清数据来源。

### 省略是确定规则

生命周期省略（lifetime elision）不是任意猜测。
编译器按固定规则补全函数、函数指针和闭包 trait 签名；规则结束后仍有歧义，就会报告缺少生命周期说明符。

函数省略规则依次是：

1. 每个省略的引用参数获得一个互不相同的输入生命周期。
2. 若全部参数中恰好只有一个输入生命周期，所有省略的输出生命周期都使用它。
3. 方法有多个输入生命周期时，只要接收者是 `&self` 或 `&mut self`，所有省略的输出生命周期都使用接收者的生命周期。

因此，`fn first(text: &str) -> &str` 可以省略，因为只有一个输入生命周期。
`fn choose(x: &str, y: &str) -> &str` 不能省略，因为规则无法判断输出来自哪个输入。
方法的接收者规则还可能让输出默认绑定到 `self`，即使另一个参数看起来更像来源。

占位生命周期 `'_` 要求编译器在当前位置推断生命周期。
它在类型路径中比完全省略更明确，例如返回 `View<'_>`；但它仍不会创建新的关系或放松借用检查。

### 借用字段把关系带进类型

结构体若保存引用，就必须在结构体定义上声明相应生命周期。
`struct View<'a> { text: &'a str }` 表示任何 `View<'a>` 都不能在 `text` 已失效后继续使用。
约束跟随这个类型进入函数参数、返回值与容器元素。

`impl<'a> View<'a>` 声明实现适用于任意 `'a`。
方法返回字段时，可以明确写 `-> &'a str`，表示结果直接借自字段的来源；写 `-> &str` 时，接收者省略规则通常把结果限制到这次 `&self` 借用。
两种签名可能都能编译，但对调用方暴露的关系不同。

借用字段适合短期视图、解析结果和避免复制的大块输入。
如果值要进入长期缓存、跨线程队列或独立于来源存储，拥有 `String`、`Vec` 或共享所有权指针通常更符合契约。
生命周期参数不能让自引用结构体自动变得安全，也不能替代所有权设计。

### 先决定所有权，再写标注

返回引用适合结果本来就是输入的一部分，而且调用方能自然保留输入的场景。
切片查找、无复制解析和集合视图都符合这种关系；签名让调用方知道结果不能脱离来源。

返回拥有型值适合函数创建新数据，或结果必须独立进入队列、缓存与异步任务的场景。
这不是对借用检查器的妥协，而是不同的 API 契约；强行借用反而会把内部存储细节传播给所有调用方。

结构体是否保存引用也应由对象的角色决定。
一次请求内使用的解析视图可以借用缓冲区，而长期领域对象通常应拥有关键字段。
先画出所有者与使用期限，再决定是否需要结构体生命周期参数。

编译器建议添加 `'a` 时，只说明签名缺少可证明的关系，不保证标注是正确修复。
若数据在函数内创建，返回引用仍然没有合法来源；若输出只来自一个输入，把其他参数绑进同一 `'a` 又会过度约束。

最小契约通常也是最稳定的契约。
它减少调用方必须同时维持的借用，让函数实现的真实数据流直接体现在类型中。
这也让后续重构更容易区分必要约束与偶然耦合。

## 示例

### 为两个输入建立共同区域

第一个例子可能返回主标签，也可能返回备用标签，所以三个引用位置使用同一个 `'a`。
两个 `String` 的词法作用域不同，但调用只在内部代码块使用结果，编译器可以选择这段共同区域。

<!-- quick -->

```rust
// file: choose_label.rs
fn choose_label<'a>(primary: &'a str, fallback: &'a str) -> &'a str {
    if primary.trim().is_empty() {
        fallback
    } else {
        primary
    }
}

fn main() {
    let primary = String::from("priority");

    {
        let fallback = String::from("untitled");
        let selected = choose_label(&primary, &fallback);
        println!("selected: {selected}");
    }
}
```

```text
selected: priority
```

<!-- /quick -->

`selected` 的可用区间不会超过两个输入借用的交集。
即使运行时选中了 `primary`，签名仍允许函数返回 `fallback`，所以调用方不能把结果带出内部代码块。

标注没有把 `primary` 缩短，也没有延长 `fallback`。
它只要求结果的每次使用都位于两者共同有效的区域内。

### 只关联真正的来源

第二个例子的结果只能来自 `text`，不会来自 `delimiter`。
签名只为文本与结果命名 `'text`，分隔符使用独立的省略生命周期。

```rust
// file: prefix_before.rs
fn prefix_before<'text>(text: &'text str, delimiter: &str) -> &'text str {
    text.split_once(delimiter)
        .map_or(text, |(prefix, _)| prefix)
}

fn main() {
    let record = String::from("account=active");

    let key = {
        let delimiter = String::from("=");
        prefix_before(&record, &delimiter)
    };

    println!("key: {key}");
}
```

```text
key: account
```

`delimiter` 在内部代码块结束时销毁，`key` 仍然有效，因为它只指向 `record`。
若把两个参数都标成 `'text`，返回值会被分隔符的短借用不必要地限制。

这种精确度对解析器和查找 API 很重要。
辅助输入只参与计算时，不应被误写成返回数据的来源。

### 在结构体中保存借用

`Header<'source>` 保存原始文本的视图，而不复制字符串。
`name` 的输出按方法省略规则绑定到 `&self`，`raw` 则明确返回来源生命周期 `'source`。

```rust
// file: header_view.rs
struct Header<'source> {
    raw: &'source str,
}

impl<'source> Header<'source> {
    fn new(raw: &'source str) -> Self {
        Self { raw }
    }

    fn name(&self) -> &str {
        self.raw
            .split_once(':')
            .map_or(self.raw, |(name, _)| name)
    }

    fn raw(&self) -> &'source str {
        self.raw
    }
}

fn main() {
    let source = String::from("content-type:text/plain");
    let header = Header::new(&source);

    println!("name: {}", header.name());
    let complete = header.raw();
    drop(header);
    println!("raw: {complete}");
}
```

```text
name: content-type
raw: content-type:text/plain
```

`complete` 借自 `source`，不是借自结构体本身，所以移动并销毁 `header` 后仍能读取它。
若方法写成省略的 `-> &str`，返回引用通常只能保证持续到 `&self` 借用结束。

结构体的生命周期参数没有拥有文本。
`source` 仍必须比所有从它派生的视图活得更久。

### 要求回调接受任意短借用

最后一个例子把规范化函数应用到多个临时字符串切片。
`for<'a>` 要求回调对每个调用选择的 `'a` 都成立，并把结果关联回该次输入。

```rust
// file: normalize_all.rs
fn normalize_all<F>(values: &[String], normalize: F) -> Vec<&str>
where
    F: for<'a> Fn(&'a str) -> &'a str,
{
    values.iter().map(|value| normalize(value)).collect()
}

fn trim_label(value: &str) -> &str {
    value.trim()
}

fn main() {
    let labels = vec![String::from(" alpha "), String::from(" beta")];
    let normalized = normalize_all(&labels, trim_label);

    println!("normalized: {normalized:?}");
}
```

```text
normalized: ["alpha", "beta"]
```

返回向量中的每个 `&str` 都借自 `labels` 中对应的 `String`。
回调不能返回某个与输入无关、寿命更短的局部切片。

普通函数 `trim_label` 满足高阶约束，因为它能处理任意有效输入借用。
这种边界常见于保存泛型闭包并在方法内部借用自身数据的 API。

## 陷阱

> **陷阱:** **用任意 `'a` 返回局部值。** `fn make<'a>() -> &'a str` 让调用方选择 `'a`，函数内新建的 `String` 无法满足任意选择；函数返回时它就会销毁。
> **修复：** 返回 `String` 等拥有型值；只有字符串字面量或静态项等确实存在于静态存储中的数据才能作为静态引用返回。

> **陷阱:** **把所有参数都绑定到同一个生命周期。** 若结果只来自第一个输入，共享 `'a` 会让无关的短借用限制结果，并制造本不需要的编译错误。
> **修复：** 画出“输出来自哪里”的关系，只给真实来源与输出使用同一名称；其余引用使用独立参数或省略生命周期。

> **陷阱:** **把 `'static` 当成延长器。** 添加 `'static` 约束、调用 `Box::leak` 或把错误藏进全局状态，会改变资源所有权，甚至永久泄漏内存，却不会修复原来的局部借用设计。
> **修复：** 在线程和回调边界移动拥有型值；可限定作用域的并发应使用作用域 API，确实需要进程期数据时才使用静态存储。

> **陷阱:** **忽略方法接收者的省略规则。** `fn choose(&self, candidate: &str) -> &str` 的输出默认关联 `self`，实现若返回 `candidate` 就会遇到生命周期错误。
> **修复：** 输出来自参数时写成 `fn choose<'a>(&self, candidate: &'a str) -> &'a str`；输出来自字段时保留接收者关系。

> **陷阱:** **为长期对象保存短借用。** 生成的缓存、任务或事件处理器常把请求体中的 `&str` 存进比请求更长寿的结构体，随后再试图用克隆、`unsafe` 或 `'static` 消除错误。
> **修复：** 先确定谁拥有数据以及对象需要活多久；跨越来源边界时保存拥有型数据或合适的共享所有权，而不是伪造生命周期。

<!-- deep -->

## 省略、边界与高阶约束

### 长于关系

长于约束（outlives bound） `'long: 'short` 表示 `'long` 至少覆盖 `'short`。
若函数要在要求 `'short` 的位置返回一个 `'long` 引用，这个约束说明较长引用可以缩短后使用。
边界描述可替换关系，不会延长底层值。

类型边界 `T: 'a` 表示 `T` 中包含的所有引用都至少对 `'a` 有效。
对 `&'a T` 来说，类型良构本身已经隐含 `T: 'a`，所以现代 Rust 中许多旧式显式边界是冗余的。
trait 边界不会以同样方式全部自动推导，不能把两类规则混为一谈。

长于关系通常出现在组合多个借用字段、泛型关联类型或将较长借用交给较短接口时。
若签名只处理一个普通 `&T`，先检查省略和隐含边界是否已经足够。

### `'static` 的两种读法

静态生命周期（static lifetime）覆盖程序的整个执行期。
字符串字面量和 `static` 项可以产生 `&'static T`，因为其引用目标在程序结束前不会销毁。
这描述引用目标的有效期。

`T: 'static` 是类型边界，表示 `T` 不含短于静态期的借用。
拥有型 `String` 满足这个边界，却仍可在普通代码块末尾被立即销毁；边界没有承诺值本身永远存在。
相反，指向局部 `String` 的 `&str` 通常不满足需要 `'static` 的线程或回调边界。

trait 对象还有默认对象生命周期规则。
在没有外层约束的类型位置，`Box<dyn Trait>` 通常等价于 `Box<dyn Trait + 'static>`；需要保存非静态借用时应明确写出合适的 `+ 'a` 或 `+ '_`。
这套默认规则不同于普通函数的三条省略规则。

### 对所有生命周期成立

高阶 trait 约束（higher-ranked trait bound，HRTB）把生命周期参数放在 `for<'a>` 中。
`F: for<'a> Fn(&'a str) -> &'a str` 表示 `F` 必须接受任意调用时才确定的输入借用，并返回与该次输入关联的借用。

普通 `F: Fn(&'a str) -> &'a str` 中的 `'a` 往往由外层调用方选择一次。
高阶约束则允许被调用函数在每次内部借用时选择新的短生命周期，这正是对局部数据反复调用回调时需要的能力。

函数指针与闭包 trait 的常见省略形式会自动得到类似高阶含义，例如 `fn(&str) -> &str` 可展开为 `for<'a> fn(&'a str) -> &'a str`。
显式写出 `for<'a>` 适合复杂泛型边界，也能让审查者看出量化发生在哪里。

### 从诊断反推契约

`E0106` 通常表示输出引用的来源仍有歧义，先检查签名缺少哪条输入输出关系。
`E0515` 常指向返回了局部数据的引用，这种错误不能靠增加任意生命周期参数修好。
`E0597` 则表示某次实际调用让被借用值过早销毁，需要调整使用区间或所有权。

修复顺序应从数据来源开始：确认输出指向谁，再确定调用方需要使用多久，最后写最小约束。
若没有任何输入或字段能拥有返回数据，就改为返回拥有型值。
只有契约本身正确后，缩短借用区间、重借用或拆分结构才有意义。

不要用 `unsafe` 把编译错误变成无法检查的承诺。
生命周期诊断往往暴露了真实的所有权缺口；绕开检查只会把失败从编译期移动到未定义行为。

<!-- /deep -->

[检查点: rust/lifetime-annotations](https://codewiki.com/zh/rust/lifetime-annotations/#checkpoint)

## 延伸阅读

- [Rust 程序设计语言：使用生命周期验证引用](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html)
- [Rust Reference：生命周期省略](https://doc.rust-lang.org/reference/lifetime-elision.html)
- [Rust Reference：trait 与生命周期边界](https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds)
- [Rustonomicon：高阶 trait 约束](https://doc.rust-lang.org/nomicon/hrtb.html)
