Rust 对象生命周期管理:垃圾回收器的秘密

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

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

开始课程

为什么 Rust 中的“垃圾回收器”并非你想的那样

\n\n

如果你来自 Python、Java 或 C# 的世界,“垃圾回收器”(garbage collector)这个词会唤起一个清晰的形象:一个后台环境定期查找并删除不再被任何对象引用的对象。在 Rust 中则完全不同。这里没有传统的 GC,但有一套所有权和借用系统,它在编译阶段就解决了同样的任务——对象生命周期管理。

\n\n

然而,生活(以及代码)比理想化的模式更复杂。有时你需要在程序的多个部分之间共享一个对象的所有权,创建循环引用,或者通过不可变指针处理可变数据。正是在这里,Rust 中的垃圾回收对象登场了:Rc、Arc、Weak 和 RefCell。让我们来弄清楚如何管理它们的生命周期,避免内存泄漏,并且不让自己头疼。

\n\n

本文是一份详细指南,面向那些希望理解 Rust 中对象生命周期管理实际工作原理的人。我们不仅会列举这些类型,还会展示真实场景,包括一个 Stack Overflow 上的热门问题(没错,就是那个有数千次浏览的问题)。

\n\n

核心概念:所有权、借用和引用计数

\n\n

在深入探讨之前,让我们回顾一下基础知识:

\n\n
    \n
  • 所有权(ownership)——每个值有且只有一个所有者。当所有者离开作用域时,该值被删除。
  • \n
  • 借用(borrowing)——你可以临时“借用”一个值的引用,而不成为所有者。限制:要么一个可变引用,要么任意数量的不可变引用。
  • \n
  • 引用计数(reference counting)——这是一种允许一个值拥有多个所有者的机制。在 Rust 中,这通过 Rc(用于单线程程序)和 Arc(用于多线程程序)实现。
  • \n
\n\n

当我们在 Rust 的上下文中谈论“垃圾回收对象”时,我们指的就是由引用计数管理的对象。它们不会在离开作用域后立即被删除,而是等待计数降至零。这就是生命周期管理——你控制着内存何时被释放。

\n\n

Rc 和 Arc:生命周期管理的基础

\n\n

Rc(Reference Counted)是一个智能指针,它允许在同一个线程内的多个代码部分之间共享一个值的所有权。每次你克隆一个 Rc,计数就会增加。当克隆离开作用域时,计数减少。当计数达到零时,该值被删除。

\n\n

一个简单的 Rc 使用示例:

\n\n
use std::rc::Rc;\n\nfn main() {\n    let data = Rc::new(vec![1, 2, 3]);\n    let a = Rc::clone(&data);\n    let b = Rc::clone(&data);\n\n    println!("引用计数: {}", Rc::strong_count(&data)); // 3\n\n    drop(a);\n    println!("删除 a 后: {}", Rc::strong_count(&data)); // 2\n}\n
\n\n

在这里,data、a 和 b 是同一个向量的三个所有者。只要其中至少有一个存活,该向量就会保留在内存中。这非常适合你无法确定哪个模块最后完成工作的情况。

\n\n

Arc(Atomic Reference Counted)的工作方式相同,但使用原子操作,这使得它对多线程程序是安全的。代价是性能略有下降。

\n\n
use std::sync::Arc;\nuse std::thread;\n\nfn main() {\n    let data = Arc::new(vec![1, 2, 3]);\n    let mut handles = vec![];\n\n    for _ in 0..3 {\n        let data_clone = Arc::clone(&data);\n        handles.push(thread::spawn(move || {\n            println!("{:?}", data_clone);\n        }));\n    }\n\n    for handle in handles {\n        handle.join().unwrap();\n    }\n}\n
\n\n

博客

书籍推荐