所有权系统
所有权(Ownership)是 Rust 最独特也是最核心的特性。它使得 Rust 能够在没有垃圾回收器的情况下保证内存安全。
什么是所有权?
在 Rust 中,每个值都有一个所有者(owner)。所有权系统遵循三条基本规则:
- Rust 中的每个值都有一个所有者
- 同一时刻,一个值只能有一个所有者
- 当所有者离开作用域时,值将被自动释放
基本示例
让我们通过代码来理解所有权:
fn main() {
let s1 = String::from("hello"); // s1 是字符串的所有者
let s2 = s1; // 所有权转移给 s2
// println!("{}", s1); // ❌ 编译错误!s1 已经无效
println!("{}", s2); // ✅ 正确,s2 现在是所有者
}
发生了什么?
当我们执行 let s2 = s1 时:
- 不是复制数据(这会很昂贵)
- 而是转移了所有权
s1变为无效,不能再使用s2成为新的所有者
内存布局
让我们可视化一下 String 在内存中的表示:
栈(Stack) 堆(Heap)
┌──────────┐
│ s1 │──────┐
├──────────┤ │ ┌─────────┐
│ ptr │──────┼───────────>│ h │
│ len: 5 │ │ │ e │
│ cap: 5 │ │ │ l │
└──────────┘ │ │ l │
│ │ o │
┌──────────┐ │ └─────────┘
│ s2 │──────┘
├──────────┤
│ ptr │──────────────────>(指向同一块堆内存)
│ len: 5 │
│ cap: 5 │
└──────────┘
重要:移动操作后,
s1的指针被失效,防止双重释放(double free)问题。
克隆(Clone)
如果确实需要深拷贝堆上的数据,可以使用 clone 方法:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 完整复制堆上的数据
println!("s1 = {}, s2 = {}", s1, s2); // ✅ 都有效
}
⚠️ 注意:clone 可能会很昂贵,因为它会复制堆上的所有数据。
栈上的数据:复制(Copy)
对于存储在栈上的简单类型,Rust 会自动复制而不是移动:
fn main() {
let x = 5;
let y = x; // 复制值
println!("x = {}, y = {}", x, y); // ✅ 都有效
}
这些类型实现了 Copy trait:
- 所有整数类型(
i32,u64等) - 布尔类型
bool - 浮点类型(
f32,f64) - 字符类型
char - 元组(如果所有成员都是
Copy的)
函数与所有权
传递值到函数
将值传递给函数会移动或复制,和赋值一样:
fn main() {
let s = String::from("hello"); // s 进入作用域
takes_ownership(s); // s 的值移动到函数
// println!("{}", s); // ❌ s 已经无效
let x = 5; // x 进入作用域
makes_copy(x); // x 被复制到函数
println!("{}", x); // ✅ x 仍然有效
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string 在这里被释放
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer 在这里离开作用域,无事发生
返回值与所有权
函数也可以转移所有权:
fn main() {
let s1 = gives_ownership(); // 函数返回值移动到 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 移入函数,返回值移动到 s3
}
fn gives_ownership() -> String {
let some_string = String::from("yours");
some_string // 返回值移动给调用者
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // 返回值移动给调用者
}
为什么需要所有权?
所有权系统解决了几个关键问题:
| 问题 | 传统语言的解决方案 | Rust 的方案 |
|---|---|---|
| 内存泄漏 | 垃圾回收器(GC) | 编译时检查 |
| 悬垂指针 | 运行时检查 | 编译时检查 |
| 数据竞争 | 锁和同步原语 | 编译时检查 |
| 双重释放 | 引用计数 | 编译时检查 |
✨ 核心优势:零成本抽象 + 内存安全 + 无需 GC
小结
- 所有权是 Rust 的核心特性
- 默认情况下,赋值和传参会移动所有权
Copy类型会被复制而不是移动- 使用
clone()可以显式深拷贝 - 当所有者离开作用域,值会被自动释放
下一步,我们将学习借用(Borrowing),这是一种在不获取所有权的情况下使用值的方式。