本页目录

引用与借用

Rust 借用检查器只认一条规则:同一时刻,要么一个可变引用,要么多个不可变引用,两者不共存(aliasing XOR mutation)。NLL 把检查粒度从词法作用域细化到实际使用区间,reborrow 让嵌套借用不需要显式标注——这三者一起构成了 Rust 的"不用 GC 的内存安全"。

& vs &mut

let mut s = String::from("hello");
let r1 = &s;                         // 不可变借用 (shared reference)
let r2 = &s;                         // 可以有多个不可变借用
println!("{} {}", r1, r2);           // OK

let r3 = &mut s;                     // 可变借用: 要求 s declared mut
// let r4 = &s;                      // ERROR: 已有 &mut, 不能再 & 
r3.push('!');

核心规则——Aliasing XOR Mutation:同一时刻,要么有多个共享引用⁠(只读),要么有恰好一个可变引用⁠(可写),两者不能共存。这条规则防止了数据竞争和 iterator invalidation。

Borrow Checker 的工作方式

编译器在 MIR (Mid-level IR) 上做 borrow checking。每个 borrow 创建时记录:借用了哪个变量,是 shared 还是 mutable,lives until 哪里。

let mut v = vec![1, 2, 3];
let first = &v[0];                   // immutable borrow of v starts
v.push(4);                           // ERROR: mutable borrow of v — first is still live
println!("{}", first);               // first last use
// first's borrow ends here

错误原因:v.push(4) 需要 &mut v,但 first 仍在 scope 中持有 &v。borrow checker 不能允许同时存在 &v 和 &mut v——即使你知道 push 不会影响 v[0],borrow checker 不知道。

Non-Lexical Lifetimes (NLL, 2018 Edition)

NLL 之前,借用的生命周期由 scope 决定(lexical——变量从声明到最后一次出现的 whole block)。NLL 改为由最后一次使用决定⁠:

let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{}", first);               // first 的最后一次使用
// first 的 borrow 在这里结束 (而不是 scope 末尾!)

v.push(4);                           // OK: first 已不再 live, borrow 已结束
// 在 NLL 之前, 这无法编译

Reborrow

当传递 &mut T 给函数时,borrow 被"reborrow":

fn push(v: &mut Vec<i32>) { v.push(5); }

let mut v = vec![1, 2, 3];
push(&mut v);                        // reborrow: &mut *v → push's &mut v
// push 结束后, reborrow 结束 → v 的 ownership 恢复

// 等价于:
// let reborrow = &mut *v;
// push(reborrow);
// drop(reborrow);  ← reborrow 在这里被 drop, v 恢复

Reference 的内存布局

&T&mut T 在运行时就是普通指针(8 bytes on 64-bit)。Borrow checker 所有检查都在编译期,零运行时开销。

let x = 42;
let r = &x;                          // 汇编: lea rax, [rsp+4]; mov [rsp+8], rax

Fat pointer: &[T]&dyn Trait 是 16 bytes——额外存储 length 或 vtable pointer。

常见陷阱

1. 不能在不可变借用期间修改原值

let mut v = vec![1, 2, 3];
let r = &v[0];
v.clear();                           // ERROR: &v (immutable) active, can't &mut v

2. Iterator invalidation 被 borrow checker 防止

let mut v = vec![1, 2, 3];
for item in &v {                     // for loop 隐式创建 &v 的借用
    v.push(4);                       // ERROR: &v active, can't &mut v
}

3. 函数返回引用——需要显式 lifetime

fn first_word(s: &str) -> &str {     // lifetime elision: actually fn<'a>(s: &'a str) -> &'a str
    &s[..s.find(' ').unwrap_or(s.len())]
}
// 返回的引用借用了 s → 编译器保证 &str 不会比 s 活得更久

参考

  • Rust Book: Chapter 4.2 — References and Borrowing
  • NLL RFC: RFC 2094
  • Polonius: github.com/rust-lang/polonius (next-gen borrow checker)

Keywords: borrow checker, shared reference, mutable reference, NLL, reborrow, aliasing XOR mutation, fat pointer