Rust有什么替代垃圾收集器的东西?

148

我知道Rust没有垃圾回收器,并且想知道当绑定超出作用域时内存是如何释放的。

所以在这个例子中,我知道当a超出作用域时,Rust会回收分配给它的内存。

{
    let a = 4
}

我遇到的问题首先是它如何发生的,其次这不是一种垃圾回收吗?它与typical垃圾回收有什么不同?


18
确定性对象生存期。类似于C ++。 - user2864740
@user2864740 那份指南已经过时了。现代的替代可能是http://doc.rust-lang.org/book/references-and-borrowing.html。 - Veedrac
Rust就像其他实用编程语言一样,是有垃圾回收机制的。只不过大家对于垃圾回收的理解方式存在误区 - IInspectable
@IInspectable 如果定义中包含了处理释放的语言都是自动垃圾回收的,那这个定义就没有意义,因为几乎所有的编程语言都符合这个条件,除了汇编语言(甚至包括C语言)。有用的是大多数开发者已经理解并遵循的定义,所以不,Rust没有外部引用计数器来停止程序执行以检查未使用的引用是否释放了内存。 - undefined
@TinSvagelj 我觉得一个垃圾回收器必须是“外部的”和“停止程序的执行”这两个要求是任意的,特别是当你解释说它是为了“检查未使用的引用并释放内存”时。后者正是Rc<T>Arc<T>(或者Box<T>)所做的事情。它们既不是外部的,也不会停止执行,因为它们没有必要这样做。如果它们这样做,观察到的结果将是相同的,我想知道你是否认为只有当垃圾回收器比必要的代价更高时,它才能被称为“垃圾回收器”。 - undefined
显示剩余3条评论
4个回答

115

垃圾回收通常是定期或按需使用的,例如当堆接近满或超过某个阈值时。然后它会根据 算法 查找未使用的变量并释放它们的内存。

Rust 在编译时就知道变量何时超出作用域或其生命周期结束,因此插入相应的 LLVM/汇编指令以释放内存。

Rust 也允许某些类型的垃圾收集,例如 原子引用计数


通过在引入变量时分配内存,并在不再需要内存时释放内存?我不太清楚你想表达什么。也许我们对GC的定义有所不同。 - Ayonix
1
他的问题是Rust的方法与典型的GC有何不同。所以我解释了什么是GC以及Rust如何在没有GC的情况下实现它。 - Ayonix
1
https://doc.rust-lang.org/book/the-stack-and-the-heap.html 很好地解释了这个问题。是的,许多东西都在堆栈中,但仅仅这样并不足以说明问题(请参见Box)。出于简单起见,我没有涉及到它,因为问题是一般性的。 - Ayonix
1
@Amomum 实际上,Rust 没有像 C 一样任命的 new() 函数,它们只是静态函数,特别是像 let x = MyStruct::new() 这样的语句会在堆栈上创建对象。真正的堆分配指示器是 Box::new()(或任何依赖于 Box 的结构)。 - Mario Carneiro
1
有哪些其他编程语言与Rust类似地处理内存管理? - still_dreaming_1
显示剩余3条评论

64
在程序中管理资源(包括内存)的基本思想是,无论采用何种策略,与不可达“对象”相关联的资源都可以被回收利用。除了内存之外,这些资源还可以是互斥锁、文件句柄、套接字、数据库连接等。
具有垃圾收集器的语言会定期扫描内存(以某种方式),找出未使用的对象,释放与其关联的资源,并最终释放这些对象所使用的内存。
Rust没有垃圾收集器,它是如何管理的呢?
Rust拥有所有权。使用仿射类型系统,它跟踪哪个变量仍然持有一个对象,并且当这样的变量超出作用域时,调用其析构函数。你可以很容易地看到仿射类型系统的效果:
fn main() {
    let s: String = "Hello, World!".into();
    let t = s;
    println!("{}", s);
}

产生:

<anon>:4:24: 4:25 error: use of moved value: `s` [E0382]
<anon>:4         println!("{}", s);

<anon>:3:13: 3:14 note: `s` moved here because it has type `collections::string::String`, which is moved by default
<anon>:3         let t = s;
                     ^

这个例子很好地说明了在任何时候,在语言层面上,所有权都是被跟踪的。这种所有权是递归的:如果你有一个Vec(即一个动态字符串数组),那么每个String都是由Vec拥有的,而Vec本身则是由一个变量或另一个对象拥有的,等等...因此,当一个变量超出其作用域时,它会递归地释放它所持有的所有资源,甚至间接地。对于Vec来说,这意味着:
1.释放与每个String相关联的内存缓冲区 2.释放与Vec本身相关联的内存缓冲区
因此,由于所有权跟踪,所有程序对象的生命周期严格绑定到一个或多个函数变量上,这些变量最终将超出其作用域(当它们所属的块结束时)。请注意,这有点乐观,使用引用计数(Rc或Arc)可以形成引用循环,从而导致内存泄漏,在这种情况下,与该循环相关的资源可能永远不会被释放。

3
有垃圾回收器的编程语言会定期扫描内存(以某种方式)。虽然很多语言是这样,但这并不是普遍情况。实时垃圾回收器是逐步扫描而非定期扫描。像 Mathematica 这样的引用计数语言根本不会扫描。 - J D
6
我认为引用计数不算是完整的垃圾回收机制,因为必须添加补充措施以避免出现泄漏循环。虽然RC通常被视为GC的一种形式,在Mathematica和Erlang中,例如,由于设计原因无法创建循环,所以RC不会泄漏。有关高级别的视角,请参见《垃圾回收的统一理论》https://www.cs.virginia.edu/~cs415/reading/bacon-garbage.pdf - J D
3
我认为“周期性”一词已经涵盖了增量情况,例如,“停止世界”算法被认为是周期性的,而“三色标记”算法被认为是增量式的。在这个上下文中,它们是相反的。 - J D
2
@JD,你深入得太多了。他的解释与GC如何工作的内部机制无关,只涉及GC和非GC语言之间的区别。你试图进行的区分是基于GC本身的实现。他试图进行的区分是抽象层面上的GC之间的区别。在这种情况下,没有必要深入探讨“周期性”在语境中的语义含义500个单词。 - Nicholas Pipitone
1
抽象来看,我们通常认为像C ++ / Rust这样使用RAII / RC的语言是非垃圾回收的。而像Java / Python / C#这样的语言则被视为垃圾回收(即使它在底层实现中使用RC)。核心区别在于,在C ++ / Rust中,RC是显式的,并且它实际上是围绕调用自己的malloc和free的5行包装器。在GC语言中,则从视图抽象出来,类是通过引用而不是按值传递的。 (而且语言规范很少提到它是否是RC或Mark-and-sweep,这通常是实现细节) - Nicholas Pipitone
显示剩余5条评论

7
在一种需要手动管理内存的语言中,栈和堆之间的区别变得至关重要。每次调用函数时,会在栈上为该函数作用域内的所有变量分配足够的空间。当函数返回时,与该函数相关联的栈帧将从栈中“弹出”,并释放内存以供未来使用。
从实际角度来看,这种无意中的内存清理被用作自动存储器,它将在函数作用域结束时被清除。
更多信息请参见: https://doc.rust-lang.org/book/the-stack-and-the-heap.html

5
使用栈是方便的,但如果所有值都在堆上创建,则可以处理确定性对象生命周期。因此,这是一种实现细节,不一定是语言策略。 - user2864740
2
你一直在使用那个词。我不认为它的意思是你所想的那样。 - Swiss
意思是我想表达的内容;它是非确定性生命周期的相反。如果有更好的短语,请提出建议。 - user2864740
感谢您的回答,我将分数给了第一个回答者,仅仅是因为他先提交了。这些信息同样有用且有效。 - rix
@user2864740 确定性对象生命周期是指在调用其析构函数后,能够准确地确定对象内存何时被清除。这与首次调用析构函数的方式无关。即使该术语与问题没有直接相关性,您仍然不断提及它。 - Swiss
显示剩余2条评论

3
一些语言采用引用计数,一些则采用垃圾回收器。Rust避免了这两种方法,相反它只允许在任何时候拥有一个变量名或别名来拥有内存位置。你可以将所有权从一个变量名转移到另一个,但是你不能有两个变量名指向同一个内存地址(除了共享所有权。Rust提供了引用计数的指针类型Rc和Arc,例如:双向链表)。
Rust使用一种相对独特的内存管理方法,其中包括内存“所有权”的概念。基本上,Rust跟踪谁可以读写内存。它知道程序何时正在使用内存,并在不再需要时立即释放内存。它在编译时强制执行内存规则,几乎不可能出现运行时内存错误。你不需要手动跟踪内存,编译器会处理它。
Discord最近在其服务中从Go切换到Rust,仅仅因为垃圾回收器导致了延迟。他们很好地解释了为什么这样做以及您将了解更多关于垃圾回收器和Rust内存系统的内容。
Discord将从Go语言转向Rust语言的原因:

https://discord.com/blog/why-discord-is-switching-from-go-to-rust#:~:text=Discord%20is%20a%20product%20focused,and%20messages%20you%20have%20read


“你不能让两个变量名指向同一个内存地址” - 我非常确定这正是 Rc/Arc 所做的。 - IInspectable
Rc和Arc允许值具有多个所有者,但受一些限制。 - Yilmaz
我不明白那如何使我的先前评论无效。无论如何,你的评论与你的声明:“每个值都有一个唯一的所有者”相矛盾。 - IInspectable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接