所有权、借用与生命周期

Rust 的内存安全承诺建立在三大支柱之上:所有权、借用与生命周期。面试中考察重点不仅仅是规则背诵,更在于理解借用检查器如何工作,以及如何在实际代码中正确标注生命周期。

核心概念速查表

概念核心规则常见误区
所有权每个值只有一个所有者;离开作用域自动释放混淆移动与复制,忽略Copy语义
借用&T 不可变,&mut T 可变;同时只能有一个可变引用或任意多个不可变引用误以为引用增加所有权计数;漏掉引用的作用域冲突
生命周期标注 'a 关联引用存活范围;省略规则适用于明确场景过度标注,或不必要地使用 'static
借用检查器编译时通过 NLL 非词法生命周期验证引用有效性忽略临时作用域,误判借用的起止点
Rust 核心三原则 所有权规则 借用:&T / &mut T 生命周期标注 &省略 借用检查器 + NLL 面试追问起点:生命周期标注 函数中的引用参数? 何时可以省略 'a ? 'static 代表什么? NLL 如何改写代码?

Q1:Rust 中所有权转移(move)与 Copy 语义有何区别?

面试官考察点
  • 是否理解值语义 vs 引用语义
  • 能否区分栈上类型与堆上类型的默认行为
  • 是否清楚 Copy trait 的约束及其影响
回答框架
  1. 指出所有权的唯一性,赋值或传参时默认发生 move(堆类型)或 Copy(栈类型)。
  2. 解释 Copy 是隐式按位复制,原始值仍然有效;move 后原绑定失效。
  3. 举例:整数、bool 实现 Copy;String、Vec 未实现,需显式 clone。
简短示例回答

“Rust 中每种类型要么实现 Copy,要么不实现。基础类型如 i32 实现 Copy,赋值后两变量都有效;而 String 未实现,传递后原变量失效。Copy 仅在类型所有字段都实现且不能被隐式 drop 时才能派生,它实际上是对栈上数据的位复制,不影响所有权。” (96字)

可能追问
  • 自定义结构体如何实现 Copy?需要什么前提?
  • 移动语义下,Vec 的赋值会发生什么?
  • FnOnce 闭包如何捕获所有权?
避坑提醒

不要把“move”与“借用”混淆。移动后原变量禁止使用,但借用后仍可通过原变量(不可变借用期间)或原变量重新借用。

Q2:同时存在不可变引用和可变引用时,编译报错。请解释规则及 NLL 的改进。

面试官考察点
  • 是否理解引用冲突的来源:读-写冲突/写-写冲突
  • 对 NLL(Non-Lexical Lifetimes)的认识
  • 能否用具体示例说明作用域优化
回答框架
  1. 一句话规则:要么一个可变引用,要么多个不可变引用,不可并存。
  2. 解释原因:防止数据竞争,Rust 在编译期通过借用检查器强制执行。
  3. 提出 NLL 允许引用在最后一次使用后即被认为结束,而非整个块。对比旧版词法生命周期。
简短示例回答

“例如先创建不可变引用后,再尝试创建可变引用会报错。但 NLL 下,若不可变引用在可变引用创建前已被最后一次使用,则编译通过。比如 let r = &s; println!(r); let r2 = &mut s; 现在合法,因为 r 在 println 后失效。” (96字)

可能追问
  • NLL 如何确定引用的最后使用点?
  • 什么情况会绕过 NLL 仍然报错?
  • 如何用作用域花括号手动缩小借用范围?
避坑提醒

不要以为 NLL 解决了所有冲突。如果不可变引用和可变引用在逻辑上交叉使用,编译器仍可能报错,学会用显式作用域或重新组织代码。

Q3:什么时候必须显式标注生命周期?省略规则是什么?

面试官考察点
  • 是否熟悉三种省略规则的适用条件
  • 能否区分输入生命周期与输出生命周期
  • 对特殊标注(如 'static)的理解深度
回答框架
  1. 明确标注的必要场景:函数返回引用且与多个参数引用关联,或结构体中包含引用字段。
  2. 列出三条省略规则:每个输入引用获得独立生命周期;如果只有一个输入,其生命周期赋予所有输出;如果是方法且返回 &self 或 &mut self,输出取自 &self。
  3. 警惕规则失效的情况,例如闭包或泛型。
简短示例回答

“比如 fn first<'a>(x: &'a str, y: &str) -> &'a str 必须标注,因为输出与一个输入有关但还有另一个输入。而 fn first_word<'a>(s: &'a str) -> &'a str 可省略,因为只有一个输入引用。” (99字)

可能追问
  • 方法中生命周期标注与 self 的关系?
  • 'static 是否永远存活?
  • 没有标注的结构体引用字段如何处理?
避坑提醒

不要轻易给返回引用标注 'static,除非真的静态生命周期。通常应使用更具体的生命周期参数,避免代码过于严格或安全性缺失。