什么是垃圾回收器,为什么Rust要摒弃它?
\n\n如果您来自Python、Java或C#的世界,那么您已经习惯了由垃圾回收器(Garbage Collector, GC)来管理内存。它就像一个隐形的“清洁工”,定期检查哪些对象不再被使用,并释放它们占用的内存。方便吗?是的。但也有一些问题:
\n\n- \n
- 不可预测的暂停。GC可能会在最不合时宜的时刻启动——当您的代码正在处理重要请求或渲染游戏帧时。 \n
- 内存开销。GC通常需要额外的内存来执行其工作。 \n
- 实时系统中的复杂性。在操作系统、驱动程序或嵌入式设备中,GC的暂停是不可接受的。 \n
Rust提供了一种截然不同的方法。它使用所有权系统(ownership)来代替垃圾回收器,该系统在编译阶段工作。编译器自行决定何时释放内存,并且没有任何运行时开销。让我们来了解它是如何工作的。
\n\n所有权系统(Ownership)——内存管理的基础
\n\n在Rust中,每个值都恰好有一个所有者——即拥有该值的变量。当所有者离开作用域(scope)时,内存会自动释放。没有垃圾回收器,没有引用计数——只有编译器和严格的规则。
\n\nRust的所有权规则
\n\n以下是编译器在编译阶段检查的三条主要规则:
\n\n- \n
- Rust中的每个值都有一个所有者。 \n
- 当所有者离开作用域时,该值会被删除(drop)。 \n
- 该值可以转移给另一个所有者(move)或借用(borrow)。 \n
fn main() {\n let s1 = String::from("Hello"); // s1 — 字符串的所有者\n let s2 = s1; // 所有权转移给 s2(move)\n // println!("{}", s1); // 错误!s1不再拥有该字符串\n println!("{}", s2); // 正确:s2是新的所有者\n} // 这里s1和s2都离开了作用域,\n // 但内存只释放一次——针对s2\n\n在Python或Java中,您只需创建对对象的另一个引用,GC稍后会处理它。而在Rust中——没有意外:编译器保证内存恰好被释放一次。
\n\n借用(Borrowing)——如何在不转移所有权的情况下使用数据
\n\n我们经常需要临时“借用”对数据的访问,而不从所有者那里拿走它。为此,Rust提供了引用(references)。它们有两种类型:
\n\n- \n
- 不可变引用(
&T):可以读取数据,但不能修改。 \n - 可变引用(
&mut T):可以读取和修改,但前提是没有其他活跃的引用。 \n
fn main() {\n let mut text = String::from("Rust");\n \n let r1 = &text; // 不可变引用\n let r2 = &text; // 另一个不可变引用——允许\n println!("{} {}", r1, r2);\n \n let r3 = &mut text; // 可变引用——可以工作,因为r1和r2不再使用\n r3.push_str(" rocks!");\n println!("{}", r3);\n \n // 但这样不行:\n // let r1 = &text;\n // let r3 = &mut text; // 错误!不能同时拥有可变引用和不可变引用\n}\n\nRust编译器不仅仅是一个静态分析器,它是一位严格但公正的老师。它不会让您搬起石头砸自己的脚。如果您试图在存在不可变引用的情况下创建可变引用,您将得到编译错误。这就是borrow checker的工作——一种替代垃圾回收器的机制。