所有权系统的计算机科学基础

原理

Rust 所有权根植于线性逻辑中 affine 类型理论。在线性逻辑中,命题是”资源”,恰好使用一次。Rust 将约束放宽为”最多一次”:每个非 Copy 值恰好被消耗(move)或销毁(drop)一次。

形式化推导规则:

Move 语义在机器码层面仅为 memcpy(栈上 24B 的 ptr+len+cap),原变量位置被编译器标记为”已移动”,后续使用触发静态错误。Drop 插入位置由 MIR 的 drop elaboration pass 确定——在所有者离开作用域的每条控制流路径上自动插入 drop(x) 调用。

对比:

语言释放方式运行时开销双释放风险
C手动 free()
C++RAII / unique_ptr
Java/GoGC trace
Rust编译期 drop 插入

编译器确定 drop 位置的算法基于支配树:找到所有者不再存活的程序点(后支配边界),插入释放。


语法

所有权转移的核心规则

let s1 = String::from("hello");
let s2 = s1;                    // move: s1 失效
// println!("{}", s1);          // 编译错误
 
let x = 5;
let y = x;                      // Copy: x 仍有效

Drop 与作用域

{
    let s = String::from("hi"); // s 获得所有权
    println!("{}", s);           // s 使用中
}                                // s.drop() 在此插入
 
fn consume(s: String) { }       // 参数接收所有权,结束时 drop

所有权图

非词法生命周期(NLL)

let mut s = String::from("hi");
let r1 = &s;
println!("{}", r1);  // r1 最后使用
let r2 = &mut s;     // OK: r1 在此前已"死亡"(NLL)

实践

力扣问题

力扣: 力扣大数运算题 — 大数所有权传递

fn multiply(a: Vec<u32>, b: Vec<u32>) -> Vec<u32> {
    // a 和 b 的所有权移入,返回新 Vec
    let mut result = vec![0; a.len() + b.len()];
    result
}

AI 自检

  1. 线性类型与 affine 类型的区别?Rust 为何选择 affine(最多一次)而非线性(恰好一次)?
  2. 编译器在什么编译阶段(HIR/MIR/LLVM IR)决定 drop 插入的位置?支配树的作用?