3.3 生命周期:证词的有效期


3.3 生命周期:证词的有效期

本节摘要:生命周期是引用有效区间的静态标注,目的是让编译器验证"每个引用都活不过被引用者"。本节讲清标注语法 'a 的读法、三条省略规则,以及结构体持有引用时必须标注的原因。读完你能读懂常见签名里的 'a 并自己补全简单标注。

为什么需要标注

第 3.2 节的悬垂案件里,编译器能自己看出问题。但有些签名它判不了:

fn longer(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } // E0106: missing lifetime specifier——返回的引用来自 x 还是 y?

调用方若传一个"短命"和一个"长命"的字符串,返回引用可能悬垂。法官缺信息,于是要求你宣誓:返回引用的有效期与两个参数中较短者绑定

fn longer<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }

读法:'a 是一个生命周期参数,三个 'a 指同一个区间;约束成立时调用才编得过——传参里短的那个决定了返回引用的寿限。标注不改变任何运行时行为,它只是写给编译器的期限声明

省略规则:为什么平时不用写

多数函数免标,靠三条省略规则:每个引用参数各自获得独立生命周期;只有一个输入引用时,输出引用默认继承它;方法(含 self 引用)的输出默认继承 self 的生命周期。所以 fn len(s: &str) -> usize 无需标注——根本没有引用返回;fn first_word(s: &str) -> &str 也免标——命中第二条。

结构体持有引用:登记有效期

struct Excerpt<'a> { part: &'a str, } let novel = String::from("漫长的判决书。全文冗长。"); let first = novel.split('。').next().unwrap(); let e = Excerpt { part: first };

Excerpt<'a> 声明:这个结构体里的引用活不过 'a,也就是不越过 novel 的生存期。想让结构体自持数据,就用 String 而非 &str——工程上后者占绝大多数,除非有明确的零拷贝理由。

'static:永不过期的证词

字符串字面量的类型是 &'static str——程序全程有效,嵌在二进制只读段。别被"static"误导,它不是存储位置,是有效期上限。

要点回顾

  • 生命周期标注是静态契约,不产生任何运行时开销;
  • 三条省略规则覆盖大多数函数,免标不等于没有期限;
  • 结构体持引用必须登记 'a,默认更倾向自持数据;
  • E0106 出现时,先问"返回的引用到底依托谁的数据",答案就是标注。

💡 关键直觉:生命周期不是管理内存,是管理"证词可信期"——引用只在被引用者活着时可信,标注把这个关系摆上明面。

结构体持有引用:标注第一次成为必填项

省略规则只覆盖函数,结构体一旦持有引用,编译器要求你写明"这段证词能活多久",因为悬垂风险从局部变成了存储性。

struct Excerpt<'a> { text: &'a str, // 声明:Excerpt 不活得比 text 久 } fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b } } fn main() { let record = String::from("证人陈述原件"); let part = Excerpt { text: &record[..4] }; // 借原件的一段 println!("摘录 {}", part.text); let l; { let temp = String::from("临时证词"); l = longest(&record, &temp); println!("较长 {}", l); } // temp 在此释放 // println!("{}", l); // E0597:l 的有效期以 temp 为限,出块即失效 }

'a 的语义要读对方向:不是"给引用续命",而是"把返回值的有效期压缩到与最短来源一致"。上面主函数里 l 在块内打印合法、出块失效,正是标注在约束调用方。编译器不会替你延长任何东西,它只是把"谁的死期决定了谁"写进法律。

三条省略规则原文转译

省略规则不是猜测,是有编号的算法,转译成判例语言如下:第一,每个引用参数各自获得独立的生命周期;第二,若只有一个输入引用,输出引用继承它;第三,若参数中有 &self,输出引用继承 self 的周期。三条都不适用时(多个引用入参且有引用出参,或结构体持引用),必须手写。值得注意的是第三条——方法返回 &self.field 永远不需要标注,这就是日常代码里方法签名普遍干净的原因。

'static 的两张面孔

'static 字面义是"活满整个程序",但它出现在两种场合,含义要分开。字符串字面量的类型是 &'static str,因为内容编进二进制的只读区,名副其实;而错误类型 Box<dyn Error + 'static> 里的约束,实际含义是"不持有非 static 的借用"——大多数owns数据而不是借数据的错误类型自动满足。真正危险的是反过来:把 'static 当成万能标注去"修"悬垂报错,例如把局部 String 泄漏成 &'static str

fn leaky() -> &'static str { let owned = String::from("庭审记录"); Box::leak(owned.into_boxed_slice()) as &'static str // 合法但内存永不回收 } fn main() { let s = leaky(); println!("{}", s); }

这份判决能通过编译,代价是那块堆内存直到进程退出才回收。Box::leak 有正当用途(一次性初始化的全局配置),但作为悬垂报错的"修复手段"出现时,几乎都是错案。正确方向通常是让函数直接返回 String,把产权交还调用方,克隆成本远比想象中便宜。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U