理解所有权
你将学到什么: Rust 的所有权系统,以及为什么
let s2 = s1会让s1失效(这不同于 C# 的引用复制);三条所有权规则、Copy与Move类型的区别、使用&和&mut的借用方式,以及借用检查器如何替代垃圾回收机制。难度: 中级
所有权是 Rust 最独特的特性,也是 C# 开发者在思维方式上需要完成的最大转变。
C# 与 Rust 内存模型对比
C# 内存模型
在 C# 中,所有的类(对象)都存储在堆(Heap)上。当你将一个变量赋值给另一个变量时,你实际上只是复制了引用(指针)。此时,两个变量都指向同一个对象。
var original = new List<int> { 1, 2, 3 };
var reference = original; // 两个变量指向同一份数据 (List)
original.Add(4);
Console.WriteLine(reference.Count); // 结果为 4 - 它们是同一个对象
Rust 所有权模型
在 Rust 中,每个值都有且仅有一个确定的所有者。当你将一个非 Copy 的值赋值给一个新的变量时,其所有权会发生转移(Moved)。
#![allow(unused)]
fn main() {
let original = vec![1, 2, 3];
let moved = original; // 所有权正式转移给 'moved'
// println!("{:?}", original); // ❌ 编译报错:original 已不再拥有该数据
}
三条所有权规则
- 每个值都有一个所有者。
- 每个值在任一时刻有且仅有一个所有者。
- 当所有者离开其作用域时,该值将被释放。(确定性的内存清理)
Copy 类型 vs Move 类型
- Copy 类型:结构简单的数值类型,如整数 (
i32)、布尔值 (bool) 和浮点数 (f64)。这些类型的赋值是直接复制,因为它们在栈上的复制开销极低。 - Move 类型:管理堆内存的复杂类型,如
String或Vec<T>。这些类型在赋值时默认发生移动,以此来防止“双重释放(double-free)”等内存错误。
借用(Borrowing)基础
借用允许你在不夺取所有权的情况下访问数据。
不可变借用 (&)
你可以同时拥有任意多个不可变借用(引用)。
#![allow(unused)]
fn main() {
fn read_value(value: &Vec<i32>) {
println!("长度:{}", value.len());
}
}
可变借用 (&mut)
在任一时刻,你只能拥有一个可变借用(引用)。这种设计可以在编译期就彻底杜绝数据竞争。
#![allow(unused)]
fn main() {
fn modify_value(value: &mut Vec<i32>) {
value.push(42);
}
}
借用检查器规则
- 允许多个不可变借用。
- 同一时刻仅允许一个可变借用。
- 在存在可变借用期间,不允许存在任何不可变借用。
RAII 与 垃圾回收 (GC)
在 C# 中,垃圾回收器 (GC) 会周期性地扫描堆内内存并清理不再被引用的对象。而在 Rust 中,一旦所有者离开作用域,内存就会被立即清理。这被称为 RAII(资源获取即初始化)。
#![allow(unused)]
fn main() {
{
let file = std::fs::File::open("data.txt")?;
// 当 'file' 离开作用域时,文件句柄会自动关闭并释放。
// 无需 'using' 语句或手动调用 'Dispose()'。
}
}
核心洞见: Rust 通过所有权系统,在没有运行时 GC 开销的情况下,实现了完美的自动化内存管理。