我知道 Rust 没有垃圾收集器,我想知道当绑定超出范围时如何释放内存。
所以在这个例子中,我知道 Rust 在超出范围时回收分配给 a
的内存。
{
let a = 4
}
我遇到的问题首先是这是如何发生的,其次这不是一种垃圾收集吗?它与 typical
垃圾回收有何不同?
垃圾收集通常定期或按需使用,例如堆接近满或高于某个阈值。然后它会根据 algorithm 查找未使用的变量并释放它们的内存。
Rust 会知道变量何时超出范围或其生命周期在编译时结束,从而插入相应的 LLVM/汇编指令以释放内存。
Rust 也允许某种垃圾收集,例如 atomic reference counting。
无论采用何种策略,在程序中管理资源(包括内存)的基本思想都是可以回收与无法访问的“对象”相关联的资源。除了内存之外,这些资源还可以是互斥锁、文件句柄、套接字、数据库连接……
带有垃圾收集器的语言会定期扫描内存(一种或另一种方式)以查找未使用的对象,释放与它们相关的资源,最后释放这些对象使用的内存。
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<String>
(即一个动态的字符串数组),那么每个 String
都由 Vec
拥有,而 Vec
本身又由一个变量或另一个对象拥有,等等......因此,当一个变量超出范围时,它会递归地释放它持有的所有资源,甚至是间接的。对于 Vec<String>
,这意味着:
释放与每个 String 关联的内存缓冲区 释放与 Vec 本身关联的内存缓冲区
因此,由于所有权跟踪,所有程序对象的生命周期都与一个(或多个)函数变量严格绑定,最终将超出范围(当它们所属的块结束时)。
注意:这有点乐观,使用引用计数(Rc
或 Arc
)可能会形成引用循环,从而导致内存泄漏,在这种情况下,与循环相关的资源可能永远不会被释放.
对于必须手动管理内存的语言,堆栈和堆之间的区别变得至关重要。每次调用函数时,都会在堆栈上为该函数范围内包含的所有变量分配足够的空间。当函数返回时,与该函数关联的堆栈帧从堆栈中“弹出”,并释放内存以供将来使用。
从实际的角度来看,这种无意的内存清理被用作自动内存存储的一种方式,它将在函数范围结束时被清除。
此处提供了更多信息:https://doc.rust-lang.org/book/the-stack-and-the-heap.html
有些语言有引用计数,有些有垃圾收集器。 Rust 避免了两者,相反,如果你想在任何时间点拥有一个内存位置,它只允许一个变量名或别名。您可以将所有权从一个变量名移动到另一个变量名,但不能有两个变量名指向同一个内存地址(共享所有权除外。Rust 提供了引用计数指针类型 Rc 和 Arc。因为有时很难找到具有您需要的生命周期的单个所有者的每个值)。
Rust 使用了一种相对独特的内存管理方法,它结合了内存“所有权”的概念。基本上,Rust 会跟踪谁可以读写内存。它知道程序何时使用内存,并在不再需要时立即释放内存。它在编译时强制执行内存规则,几乎不可能出现运行时内存错误。您不需要手动跟踪内存。编译器会处理它。
Discord 最近在其一项服务中从 Go 切换到 Rust,只是因为垃圾收集器导致延迟。他们很好地解释了他们这样做的原因,您将了解有关垃圾收集器和 rust 内存系统的更多信息:
Rc
/Arc
所做的。
不定期副业成功案例分享
new()
函数,它们只是静态函数,特别是像let x = MyStruct::new()
这样的东西在堆栈上创建它的对象。堆分配的 real 指标是Box::new()
(或任何依赖于 Box 的结构)。