Rust中的内存管理:为什么没有垃圾回收器以及它是如何工作的

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

什么是垃圾回收器,为什么Rust要摒弃它?

\n\n

如果您来自Python、Java或C#的世界,那么您已经习惯了由垃圾回收器(Garbage Collector, GC)来管理内存。它就像一个隐形的“清洁工”,定期检查哪些对象不再被使用,并释放它们占用的内存。方便吗?是的。但也有一些问题:

\n\n
    \n
  • 不可预测的暂停。GC可能会在最不合时宜的时刻启动——当您的代码正在处理重要请求或渲染游戏帧时。
  • \n
  • 内存开销。GC通常需要额外的内存来执行其工作。
  • \n
  • 实时系统中的复杂性。在操作系统、驱动程序或嵌入式设备中,GC的暂停是不可接受的。
  • \n
\n\n

Rust提供了一种截然不同的方法。它使用所有权系统(ownership)来代替垃圾回收器,该系统在编译阶段工作。编译器自行决定何时释放内存,并且没有任何运行时开销。让我们来了解它是如何工作的。

\n\n

所有权系统(Ownership)——内存管理的基础

\n\n

在Rust中,每个值都恰好有一个所有者——即拥有该值的变量。当所有者离开作用域(scope)时,内存会自动释放。没有垃圾回收器,没有引用计数——只有编译器和严格的规则。

\n\n

Rust的所有权规则

\n\n

以下是编译器在编译阶段检查的三条主要规则:

\n\n
    \n
  • Rust中的每个值都有一个所有者。
  • \n
  • 当所有者离开作用域时,该值会被删除(drop)。
  • \n
  • 该值可以转移给另一个所有者(move)或借用(borrow)。
  • \n
\n\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
\n\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\n

Rust编译器不仅仅是一个静态分析器,它是一位严格但公正的老师。它不会让您搬起石头砸自己的脚。如果您试图在存在不可变引用的情况下创建可变引用,您将得到编译错误。这就是borrow checker的工作——一种替代垃圾回收器的机制。

博客

书籍推荐