所有权
Rust 最核心的概念,也是最多人卡住的地方。这篇只记录理解它的路径。
三条规则
- 每个值都有一个所有者(owner)
- 同一时刻只能有一个所有者
- 所有者离开作用域时,值被丢弃(drop)
看起来简单,真正让人卡住的是「借用」和「生命周期」这两层。
移动(move)到底发生了什么
rust
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1); // 编译错误:borrow of moved value1
2
3
4
2
3
4
很多人第一反应是「这不是浅拷贝吗」。不完全是。
String 在栈上存的是「指针 + 长度 + 容量」,真实字符数据在堆上。 赋值时 Rust 把栈上那三个字段复制过去,同时把 s1 标记为失效。
所以它不是浅拷贝(那样会导致双重释放),也不是深拷贝(那样有运行时开销), 而是移动:所有权转移,旧变量作废。
想保留原变量
用 s1.clone(),但要清楚这是显式付出了一次堆分配的成本。 如果你频繁需要 clone,通常说明数据结构设计有问题。
借用:把「读」和「写」分开
借用规则可以概括成一句话:
要么有任意多个不可变借用,要么有恰好一个可变借用,两者不能同时存在。
rust
let mut v = vec![1, 2, 3];
let a = &v; // 不可变借用
let b = &v; // 再来一个,没问题
println!("{a:?} {b:?}"); // a、b 最后一次使用
let c = &mut v; // 现在可以可变借用了
c.push(4);1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
关键在最后一段:a 和 b 在 println! 之后就再也没有被使用, 所以它们的借用在那一行就结束了。这就是 NLL(非词法生命周期)。
初学者最常犯的错是以为「借用会持续到作用域结束」, 实际上编译器看的是最后一次使用的位置。
常见报错对照
| 报错 | 原因 | 解法 |
|---|---|---|
borrow of moved value | 值被移动了 | clone(),或改成传引用 |
cannot borrow as mutable | 同时存在不可变借用 | 缩短借用范围,调整使用顺序 |
does not live long enough | 借用的东西先被释放 | 把数据的所有者提到更外层 |
value borrowed here after move | 移动后又用了原变量 | 传引用,或把使用提到移动之前 |
该不该用 Rc / RefCell
如果你发现自己在跟借用检查器较劲,先别急着上 Rc<RefCell<T>>,问自己三个问题:
| 问题 | 如果是 | 该用 |
|---|---|---|
| 真的需要共享所有权吗? | 是 | Rc<T> / Arc<T> |
| 真的需要在共享的同时修改吗? | 是 | Rc<RefCell<T>> |
| 这个结构真的需要是图吗? | 否 | 重新设计数据布局 |
我的经验是:大部分时候答案是「不需要」。把数据拆开、把所有权理清楚, 代码往往比堆一层 RefCell 更清楚。
小结
- 移动 = 栈上字段复制 + 旧变量作废,不涉及堆数据拷贝
- 借用冲突看的是最后一次使用,不是作用域边界
- 遇到借用错误先想「所有者是谁」,别急着 clone
下一篇讲生命周期,它是借用规则在函数签名上的延伸。