主题
Rust 深入理解 Ownership(所有权)与 Borrowing(借用)
如果说 Rust 有一道“新手门槛”,那一定是:
Ownership(所有权) 和 Borrowing(借用)
它们不是语法技巧,而是 Rust 整个内存模型的核心。 一旦理解,你会发现 Rust 并不反人类,反而非常严谨。
一、为什么 Rust 一定要有「所有权」?
在 Java / JS 中:
- Java:GC 自动回收
- JS:引擎自动管理内存
而 Rust 选择了一条更“硬核”的路:
不用 GC,也不让你手动 free,而是在编译期“证明”代码是安全的
所有权,就是 Rust 用来完成这件事的工具。
二、Ownership(所有权)到底是什么?
1️⃣ 所有权的直觉理解
你可以把 所有权 想象成:
“这块内存的唯一负责人”
- 谁拥有它
- 谁负责释放它
- 谁说了算
Rust 的规则非常严格:
一块资源,只允许一个负责人
2️⃣ 所有权的三条铁律(必须牢记)
text
1. 每个值都有一个所有者
2. 同一时间只能有一个所有者
3. 所有者离开作用域,值会被释放这三条规则,贯穿你写的每一行 Rust 代码。
三、Move(所有权转移)——新手最容易踩坑的地方
示例 1:String 的 move
rust
let s1 = String::from("Rust");
let s2 = s1;
// println!("{}", s1); // ❌ 编译错误发生了什么?
String数据在 堆上let s2 = s1并不会复制字符串- 而是 转移所有权(move)
结果是:
| 变量 | 是否还能用 |
|---|---|
| s1 | ❌ 不能 |
| s2 | ✅ 可以 |
👉 Rust 直接禁止你再用 s1, 从根源上杜绝 释放后使用(use after free)。
示例 2:函数参数中的 move
rust
fn print_str(s: String) {
println!("{}", s);
}
print_str(s1); // s1 被 move
let s = String::from("Hello");
print_str(s);
// println!("{}", s); // ❌函数参数默认会获取所有权。
你可以把它理解为:
把变量“交给函数去处理”, 函数用完就会负责释放。
四、Copy vs Move:为什么 i32 不会出问题?
rust
let x = 5;
let y = x;
println!("{}", x); // ✅原因:i32 是 Copy 类型
| 特性 | Copy 类型 |
|---|---|
| 存储位置 | 栈 |
| 拷贝成本 | 极低 |
| 行为 | 按位复制 |
Copy 类型特点:
- 存在 栈上
- 复制成本极低
- 赋值时直接复制一份值
Rust 中常见的 Copy 类型:
i32u64boolchar(i32, i32)
而下面这些不是 Copy:
StringVec<T>HashMap<K, V>
String / Vec / HashMap 都是 Move 类型。
五、Clone:显式的“我知道我在复制”
rust
let s2 = s1.clone();clone() 表示:
“我明确知道要复制一份堆数据,并愿意承担性能成本”
这是 Rust 的设计哲学之一:
让昂贵操作显式出现
六、Borrowing(借用):不拿走所有权,也能用数据
如果只有所有权,Rust 会非常难用。 所以 Rust 引入了 Borrowing(借用)。
1️⃣ 什么是借用?
rust
let s = String::from("Rust");
let r = &s;&s 表示:
- 不获取所有权
- 只是“借来看一眼”
你可以把借用理解成:
“临时访问权”
2️⃣ 不可变借用(&T)
rust
fn print_string(s: &String) {
println!("{}", s);
}
let s = String::from("Rust");
print_string(&s);
println!("{}", s); // ✅ 仍然可用特点:
- 只读
- 可以同时存在多个
- 不影响所有者
rust
let r1 = &s;
let r2 = &s;3️⃣ 可变借用(&mut T)
rust
fn add_to_string(s: &mut String) {
s.push_str(" + 追加");
}
let mut s = String::from("Rust");
add_to_string(&mut s);特点:
- 可以修改数据
- 同一时间只能有一个
- 不能和不可变借用共存
七、借用规则(Rust 安全性的核心)
同一时间:
- ✅ 多个不可变借用
- ❌ 不可变借用 + 可变借用
- ❌ 多个可变借用
- 错误示例
rust
let mut s = String::from("Rust");
let r1 = &s;
let r2 = &mut s; // ❌ 编译错误Rust 在编译期就阻止了“数据竞争”。
八、什么时候该用 &、&mut,什么时候该 move(重点)
这是新手最容易迷糊、但最重要的一节。
你可以用下面这套 决策模型 来判断。
1️⃣ 什么时候用 &T(不可变借用)
只读,不修改,不负责释放
rust
fn print_string(s: &String) {}适用场景:
- 打印
- 校验
- 计算
- 查看状态
一句话总结:
“我只是用一下,不碰、不拿、不背锅”
2️⃣ 什么时候用 &mut T(可变借用)
我要修改原数据,但不想拥有它
rust
fn add_to_string2(s: &mut String) {
s.push_str(" + 追加");
}这里:
- 所有权 ❌ 没有转移
- 修改的是原变量
- 修改权是临时的
一句话总结:
“我改一下,用完就还你”
3️⃣ 什么时候该 move(直接用 T)
函数要“接管”这份数据
rust
fn consume(s: String) {}适用场景:
- 函数内部创建并返回
- 需要长期保存
- 明确语义上的“消费”
一句话总结:
“交给我,我负责到底”
4️⃣ 一张终极判断表((强烈推荐)请记住)
问自己三个问题:
我只是读取吗? →
&T我需要修改,但不想负责释放吗? →
&mut T我需要长期持有 / 存储 / 返回 / 转移责任吗? →
T(move)
| 需求 | 选择 |
|---|---|
| 只读 | &T |
| 修改原数据 | &mut T |
| 接管 / 返回 / 保存 | T(move) |
| 简单值(i32 等) | 直接传 |
九、可变借用(&mut) ≠ 所有权转移(非常重要)(新手常见误解)
rust
fn add_to_string2(s: &mut String) {
s.push_str(" + 追加");
}
- 这里发生的是:
s始终属于调用者- 函数只是暂时借用“修改权限”
- 没有释放责任
👉 可变借用 ≠ 拿走所有权
这是很多新手的误区。
十、为什么不能返回局部变量的引用?
rust
fn generate_str() -> &String {
let s = String::from("字符串");
&s // ❌
}原因一句话就够:
你在返回一个指向“即将被释放内存”的引用
s 在函数结束时就被释放了, Rust 通过生命周期检查直接禁止这种行为。
十一、终极总结(新手必记)
- 所有权:唯一负责释放资源
- move:责任转移
- copy:值复制
- borrow:临时访问
- &:只读借用
- &mut:可写借用
如果你能在写代码时下意识回答一句: “现在谁是 owner?” 那你已经真正入门 Rust 了。
结语
Rust 的所有权与借用,本质是在回答三件事:
- 谁负责释放?
- 谁能访问?
- 能不能修改?
Rust 不靠运行时兜底,而是在编译期把所有问题解决掉。
一旦你建立了这套心智模型, 后面的生命周期、并发、异步,都会变得自然。