【问题标题】:What does Rust have instead of a garbage collector?Rust 有什么而不​​是垃圾收集器?
【发布时间】:2015-12-17 02:27:30
【问题描述】:

我知道 Rust 没有垃圾收集器,我想知道当绑定超出范围时如何释放内存。

所以在这个例子中,我知道 Rust 在超出范围时会回收分配给“a”的内存。

{
    let a = 4
}

我遇到的问题首先是这是怎么发生的,其次这不是一种垃圾收集吗?它与“典型”垃圾收集有何不同?

【问题讨论】:

标签: rust


【解决方案1】:

无论采用何种策略,在程序中管理资源(包括内存)的基本理念都是可以回收与无法访问的“对象”相关的资源。除了内存,这些资源还可以是互斥锁、文件句柄、套接字、数据库连接......

带有垃圾收集器的语言会定期扫描内存(一种或另一种方式)以查找未使用的对象,释放与它们相关的资源,最后释放这些对象使用的内存。

Rust 没有 GC,它是如何管理的?

Rust 拥有所有权。使用affine type system,它会跟踪哪个变量仍然持有对象,并且当这样的变量超出范围时,调用它的析构函数。您可以很容易地看到仿射类型系统的效果:

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&lt;String&gt;(即一个动态的字符串数组),那么每个String都归Vec所有,而Vec本身又归一个变量或另一个对象所有,等等。 .. 因此,当一个变量超出范围时,它会递归地释放它持有的所有资源,甚至是间接的。对于Vec&lt;String&gt;,这意味着:

  1. 释放与每个String关联的内存缓冲区
  2. 释放与Vec 本身关联的内存缓冲区

因此,由于所有权跟踪,所有程序对象的生命周期都与一个(或多个)函数变量严格绑定,最终将超出范围(当它们所属的块结束时)。

注意:这有点乐观,使用引用计数(RcArc)可能会形成引用循环,从而导致内存泄漏,在这种情况下,与循环相关的资源可能永远不会被释放。

【讨论】:

  • “带有垃圾收集器的语言会定期扫描内存(一种或另一种方式)”。许多人这样做,但总的来说并非如此。实时垃圾收集器以增量方式而不是定期进行扫描。像 Mathematica 这样的引用计数语言根本不扫描。
  • “我不认为引用计数是一个完整的垃圾收集机制,因为它必须被补充以避免泄漏循环”。 RC 通常被认为是 GC 的一种形式。例如,在 Mathematica 和 Erlang 中,循环不能通过设计创建,因此 RC 不会泄漏。更高层次的观点,请参阅“垃圾收集的统一理论”cs.virginia.edu/~cs415/reading/bacon-garbage.pdf
  • “我看不出周期性不包括增量情况”。例如,停止世界算法将被视为周期性,而三色标记被视为增量。在这种情况下,它们是对立的。
  • @JD 你说得太深了。他的解释与 GC 的工作原理无关,只与 GC 和非 GC 语言之间的差异有关。您试图做出的区分是基于 GC 本身的实现。他试图做出的区分是抽象的 GC 之间的区别。在这种情况下,没有必要深入研究“周期性”的语义含义。
  • 抽象地说,我们通常将使用 RAII/RC 的 C++/Rust 等语言视为非垃圾收集。以及诸如 Java/Python/C# 之类的语言将进行垃圾收集(即使它使用 RC 作为底层实现)。核心区别在于,在 C++/Rust 中,RC 是显式的,它实际上是一个 5 行包装器,围绕调用 malloc 和释放自己。在 GC 语言中,它是从视图中抽象出来的,并且类是通过引用而不是值传递的。 (而且语言规范很少提到它是 RC 还是 Mark-and-sweep,这通常是一个实现细节)
【解决方案2】:

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

Rust 会知道变量何时超出范围或其生命周期在编译时结束,从而插入相应的 LLVM/汇编指令以释放内存。

Rust 还允许进行某种垃圾回收,例如 atomic reference counting

【讨论】:

  • 通过在引入变量时分配内存,在不再需要内存时释放内存?我真的不知道你想说什么。或许我们对什么是 GC 有不同的看法。
  • 他的问题是 Rust 的方法与典型的 GC 有何不同。所以我解释了 GC 是什么,以及 Rust 如何在没有 GC 的情况下做到这一点。
  • doc.rust-lang.org/book/the-stack-and-the-heap.html 很好地解释了它。是的,很多东西都在堆栈中,但更不用说是不够的指标(见方框)。为了简单起见,我把它省略了,因为这个问题是普遍问的
  • @Amomum 实际上,Rust 没有像 C 一样的 new() 函数,它们只是静态函数,特别是像 let x = MyStruct::new() 这样的东西在堆栈上创建它的对象。堆分配的real指标是Box::new()(或任何依赖于Box的结构)。
  • 还有哪些其他语言处理内存管理的方式与 Rust 类似?
【解决方案3】:

对于必须手动管理内存的语言,堆栈和堆之间的区别变得至关重要。每次调用函数时,都会在堆栈上为该函数范围内包含的所有变量分配足够的空间。当函数返回时,与该函数关联的堆栈帧从堆栈中“弹出”,并释放内存以供将来使用。

从实际的角度来看,这种无意的内存清理被用作自动内存存储的一种方式,它将在函数作用域结束时被清除。

这里有更多信息: https://doc.rust-lang.org/book/the-stack-and-the-heap.html

【讨论】:

  • 虽然使用堆栈很方便,但如果所有值都“在堆上创建”,则仍然可以处理确定性对象生命周期。因此它是一个实现细节;不一定是语言策略。
  • 你一直在用那个词。我不认为它意味着你认为它的意思。
  • 表示what I wish to express;与不确定的生命周期相反。提出更好的短语。
  • 感谢您的回答,我给第一个分数只是因为它是第一个提交的。这些信息同样有用且有效。
  • @user2864740 确定性对象生命周期是指能够准确判断对象的内存何时会在其析构函数被调用后被清除。它与首先如何调用该析构函数无关。即使它对问题没有直接意义,您仍会不断重复提出同一个术语。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-10
  • 1970-01-01
  • 2022-01-12
  • 1970-01-01
  • 2011-06-27
  • 1970-01-01
  • 2023-03-07
相关资源
最近更新 更多