一句话总结
多数函数不用写生命周期, 是因为编译器有一套**省略规则(elision rules)**替你补全.
当结构体要持有引用时, 必须给结构体标注生命周期--这等于声明"该结构体不能比它借用的数据活得久".
'static 则表示"活整个程序", 别被它的字面意思误导.
省略规则: 为什么大多数时候不用写
上一篇说"涉及引用的函数要标生命周期", 但实际写过的大量带引用函数其实都没标过. 原因是编译器内置了三条省略规则, 能覆盖绝大多数常见情形, 自动补全标注:
- 每个引用参数各自分配一个生命周期参数.
- 若只有一个输入生命周期, 它被赋给所有输出引用.
- 若有
&self或&mut self(即方法), self 的生命周期被赋给所有输出引用.
|
为什么 longest 不能被省略
回看上一篇的 longest(x: &str, y: &str): 它有两个输入引用, 规则②(单输入)不适用, 规则③(方法)也不适用--编译器无法确定输出该绑哪个输入, 省略规则失效, 于是要求手动标注.
记住: 需要写标注的, 正是省略规则覆盖不到的"有歧义"场景.
省略规则像表单的"智能默认值": 单个引用输入时, 输出"显然"借的就是它, 编译器替你填好; 多个引用时它不敢猜, 才把空格留给程序员.
所以感觉"大部分函数不用写"--不是不需要, 是被默默填上了.
结构体持有引用: 必须标注
当结构体的字段是引用而非拥有的值时, 必须为结构体声明生命周期. 这是进阶篇真正高频用到标注的地方:
|
struct Excerpt<'a> 读作: "Excerpt 实例的存活时间, 不能超过它 part 字段所借用的数据." 这迫使编译器在任何使用 Excerpt 的地方, 都确保被借的字符串还活着.
设计抉择: 借用还是拥有
结构体持有引用(&'a str)能省去复制, 但带来生命周期约束--它会"绑住"被借的数据, 使用处处受 'a 牵制.
很多时候, 让结构体拥有数据(用 String 而非 &str)反而更省心. "持有引用的结构体"是把双刃剑: 性能换来约束. 初学阶段, 除非确有必要, 优先让结构体拥有数据.
方法中的生命周期
给带生命周期的结构体写方法, 语法上要在 impl 后声明 'a(和泛型参数一样). 但得益于省略规则③, 方法体内多数时候不用重复标注:
|
'static: 活整个程序
'static 是一个特殊的, 预定义的生命周期, 表示"该引用在整个程序运行期间都有效". 最典型的 'static 数据是字符串字面量--它们被编入二进制, 从程序启动到结束一直存在:
|
两个关于 'static 的常见误解
误解一: 看到报错让我加 'static, 我就加. 编译器建议加 'static 往往不是真正的解法--它可能在提示"这个引用活得不够久", 盲目加 'static 只会把错误推到别处. 先想清楚数据到底活多久.
误解二: T: 'static 约束 = 必须是 'static 数据. 当 'static 作为泛型约束出现(如 T: 'static), 它的意思是"T 不包含任何非 'static 的借用"--一个拥有所有权的 String 就满足 T: 'static, 因为它不借用任何东西. 这与"引用要活整个程序"是两回事. 在「异步 Rust」分类会反复遇到这个约束.
快速回顾
- 省略规则: 三条规则(逐参数分配, 单输入贯通, self 优先)替你补全标注, 故多数函数不用写.
- 何时必须写: 省略规则覆盖不到的歧义场景--典型是多个引用输入且返回引用(如 longest).
- 结构体持有引用: 必须标
struct S<'a>, 声明"实例不能比所借数据活得久"; 常可改为拥有数据来回避. - 方法:
impl<'a> S<'a>声明, 方法体多由规则③自动绑定 self, 无需重复标. - 'static: 表示活整个程序(如字面量); 作为泛型约束
T: 'static意为"不含非 'static 借用", 拥有所有权的值即满足.
动手练习
-
这个函数需要手写生命周期吗? 用省略规则解释.
fn trim_prefix(s: &str, n: usize) -> &str { &s[n..] }
参考答案
不需要. 只有一个引用输入 s(n 不是引用), 命中省略规则②: 单个输入生命周期被赋给输出. 编译器自动补全为 fn trim_prefix<'a>(s: &'a str, n: usize) -> &'a str, 无歧义, 无需手写.
-
下面的结构体定义为什么编译不过? 怎么修?
struct Parser {
input: &str,
}
参考答案
结构体字段是引用 &str, 但没有声明生命周期, 编译器不知道这个引用(以及持有它的结构体)能活多久. 报 "missing lifetime specifier". 修法:
|
若不想被生命周期约束, 也可改为拥有数据: input: String. 两种选择对应"借用 vs 拥有"的设计权衡.
- 判断真假: "给一个函数参数加上
'static, 能让传入的临时变量活得更久."
参考答案
假. 生命周期标注是描述性的, 'static 也不例外--它不会延长任何数据的实际寿命. 给参数标 &'static T 反而是施加约束: 要求调用方传入的引用本身就活整个程序, 否则编译失败. 它是"提条件", 不是"给能力".
- 一个拥有所有权的
String满足泛型约束T: 'static吗? 为什么?
参考答案
满足. T: 'static 的含义是"T 不包含任何非 'static 的借用", 而不是"T 必须活整个程序". 一个 String 拥有自己的数据, 不借用任何外部引用, 因此满足 T: 'static.
这个约束在把数据交给线程或异步任务(它们可能活得很久)时很常见--要求传入的东西"自给自足, 不依赖外部借用".