栈内存优点是分配释放简单效率高,缺点是无法分配动态内存大小,空间有限以及生命周期太短’
而堆正好互补。
Rust:重新审视了堆内存的生命周期,发现大部分堆内存的需求是动态大小,一小部分需求是更长的生命周期。它默认将堆内存的生命周期和使用它的栈内存的生命周期绑定在一起,并留了Box::leak机制,让堆内存在必要的时候,可以有能力超出帧存活期的生命周期。
值可能会在栈上创建,也可能在堆上创建。
像原生类型比如字符、数组、元组(tuple)等,以及开发者自定义的固定大小的结构体(struct)、枚举(enum) 等这些在编译时可以确定大小的值会在栈上创建。
无法确定大小,或需要更长的生命周期的值最好在堆上创建。
值的使用
之前我们学习所有权的时候,了解到一个值如果没有实现Copy,在赋值,传参,函数返回的时候会被Move。
其实copy和move都是基于浅复制。只不过move是复制了栈上的指针(指向堆内存),并且删除原来的引用;而copy是直接浅复制栈上的对象,不涉及堆内存。所以都是比较高效的。
比如动态数组在进行多次删除后,记得shrink_to_fit,节省一下堆内存资源。
值的销毁
之前有提到,当所有者离开作用域,它拥有的值会被丢弃。那Rust 到底是咋丢弃的呢?这里要提到Drop trait,当一个值要被释放,它的 Drop trait 会被调用。
struct结构体在调用 drop() 时,会依次调用每一个字段的 drop() 函数,如果字段又是一个复杂的结构或者集合类型,就会递归下去,直到每一个字段都释放干净。
所有权机制规定了,一个值只能有一个所有者,所以在释放堆内存的时候,整个过程简单清晰,就是单纯调用 Drop trait,不需要有其他顾虑。这种对值安全,也没有额外负担的释放能力,是 Rust 独有的。
Rust 在内存管理方面的设计像是公司管理,一个个简单死板的规章制度, 大量简单的制度能构造出一个高效且不出错的系统。
释放其他资源
比如我们创建一个文件 file,往里面写入 “hello world”,当 file 离开作用域时,不但它的内存会被释放,它占用的资源、操作系统打开的文件描述符,也会被释放,也就是文件会自动被关闭。
use std::fs::File;
use std::io::prelude:😗;
fn main() -> std::io::Result<()> {
let mut file = File::create(“foo.txt”)?;
file.write_all(b"hello world")?;
Ok(())
}
这个比较厉害了,无论C还是 Golang,你都需要显式地关闭文件,避免资源的泄露。这是因为,即便 GC 能够帮助开发者最终释放不再引用的内存,它并不能释放除内存外的其它资源。Golang里可能还需要用defer 来close一下。
虽然只是节省了一个小小的close,但是一个复杂的业务代码里,一旦,多个变量和多种异常或者错误叠加,我们忘记释放资源的风险敞口会成倍增加,很多死锁或者资源泄露就是这么产生的。