引用与生命周期的底层实现
原理
Rust 引用在运行时与 C 指针完全等效——都是 8B 虚拟地址。区别在编译期:borrow checker 为 &T 附加 noalias 属性传给 LLVM,为 &mut T 附加 unique + noalias 属性。
LLVM noalias 属性告知优化器:通过该指针访问的内存没有其他指针同时访问。这使得:
- Load/store 可以重排
- 值可以缓存到寄存器不重新加载
- 向量化时可以忽略别名冲突
借用规则的检查在 MIR 层执行(NLL borrowck),通过求解变童(variable)在每条语句上的存活性约束来工作。每条 & 生成 borrow,每条 &mut 生成 unique borrow,borrows 与所有权之间的关系用 disjoint intervals 表示。
宽指针:&str 和 &[T] 是 fat pointer(16B),包含数据指针和长度。&dyn Trait 包含数据指针和 vtable 指针。这些在调用时通过寄存器对传递。
语法
引用与指针
let x = 5;
let r: &i32 = &x; // 编译后等价于 *const i32
let v = *r; // 解引用
// 裸指针(unsafe)
let p: *const i32 = &x as *const i32;
unsafe { println!("{}", *p); }借用检查的核心规则
| 规则 | 示例 |
|---|---|
| 共享借用可复制 | let r1 = &s; let r2 = &s; |
| 独占借用唯一 | let r = &mut s; |
| 二者不能共存 | &s 和 &mut s 不可同时有效 |
| 被借时不可 move | 有 &s 时不能 let t = s |
noalias 优化效果
fn add(a: &mut i32, b: &i32) { *a += *b; }
// 编译器可为 a 生成 noalias load/store
// 因为 &mut 保证无其他引用指向同一位置fat pointer 布局
| 类型 | 栈大小 | 组成部分 |
|---|---|---|
&T | 8B | 数据指针 |
&[T] | 16B | ptr + len |
&str | 16B | ptr + len |
&dyn Trait | 16B | data ptr + vtable ptr |
&mut T | 8B | 数据指针 |
实践
力扣问题
力扣: 力扣DFS — 引用传递
AI 自检
noalias属性具体影响 LLVM 哪些优化 pass?举一个特定优化的例子。&str的 fat pointer 为何是 16B 而非 8B?长度信息存在的必要性是什么?