4.4.3 Drop 释放资源
Drop 释放资源
在 Rust 中,我们之所以可以一拳打跑 GC
的同时一脚踢翻手动资源回收,主要就归功于 Drop
特征,同时它也是智能指针的必备特征之一。
1. 学习目标
如何自动和手动释放资源及执行指定的收尾工作
2. Rust 中的资源回收
在一些无 GC
语言中,程序员在一个变量无需再被使用时,需要手动释放它占用的内存资源,如果忘记了,那么就会发生内存泄漏,最终臭名昭著的
OOM
问题可能就会发生。
而在 Rust 中,你可以指定在一个变量超出作用域时,执行一段特定的代码,最终编译器将帮你自动插入这段收尾代码。这样,就无需在每一个使用该变量的地方,都写一段代码来进行收尾工作和资源释放。不禁让人感叹,Rust 的大腿真粗,香!
没错,指定这样一段收尾工作靠的就是咱这章的主角 - Drop
特征。
3. 一个不那么简单的 Drop 例子
1 |
|
上面代码虽然长,但是目的其实很单纯,就是为了观察不同情况下变量级别的、结构体内部字段的
Drop
,有几点值得注意:
Drop
特征中的drop
方法借用了目标的可变引用,而不是拿走了所有权,这里先设置一个悬念,后边会讲- 结构体中每个字段都有自己的
Drop
来看看输出:
1 |
|
嗯,结果符合预期,每个资源都成功的执行了收尾工作,虽然
println!
这种收尾工作毫无意义 =,=
3.1 Drop 的顺序
观察以上输出,我们可以得出以下关于 Drop
顺序的结论
- 变量级别,按照逆序的方式,
_x
在_foo
之前创建,因此_x
在_foo
之后被drop
- 结构体内部,按照顺序的方式,结构体
_x
中的字段按照定义中的顺序依次drop
3.2 没有实现 Drop 的结构体
实际上,就算你不为 _x
结构体实现 Drop
特征,它内部的两个字段依然会调用
drop
,移除以下代码,并观察输出:
1 |
|
原因在于,Rust 自动为几乎所有类型都实现了 Drop
特征,因此就算你不手动为结构体实现
Drop
,它依然会调用默认实现的 drop
函数,同时再调用每个字段的 drop
方法,最终打印出:
1 |
|
4. 手动回收
当使用智能指针来管理锁的时候,你可能希望提前释放这个锁,然后让其它代码能及时获得锁,此时就需要提前去手动
drop
。 但是在之前我们提到一个悬念,Drop::drop
只是借用了目标值的可变引用,所以,就算你提前调用了
drop
,后面的代码依然可以使用目标值,但是这就会访问一个并不存在的值,非常不安全,好在
Rust 会阻止你:
1 |
|
报错如下:
1 |
|
如上所示,编译器直接阻止了我们调用 Drop
特征的
drop
方法,原因是对于 Rust
而言,不允许显式地调用析构函数(这是一个用来清理实例的通用编程概念)。好在在报错的同时,编译器还给出了一个提示:使用
drop
函数。
针对编译器提示的 drop
函数,我们可以大胆推测下:它能够拿走目标值的所有权。现在来看看这个猜测正确与否,以下是
std::mem::drop
函数的签名:
1 |
|
如上所示,drop
函数确实拿走了目标值的所有权,来验证下:
1 |
|
Bingo,完美拿走了所有权,而且这种实现保证了后续的使用必定会导致编译错误,因此非常安全!
细心的同学可能已经注意到,这里直接调用了 drop
函数,并没有引入任何模块信息,原因是该函数在std::prelude
里。
事实上,能被显式调用的drop(_x)函数只是个空函数,在拿走目标值的所有权后没有任何操作。而由于其持有目标值的所有权,在drop(_x)函数结束之际,编译器会执行_x真正的析构函数,从而完成释放资源的操作。换句话说,drop(_x)函数只是帮助目标值的所有者提前离开了作用域。https://doc.rust-lang.org/std/mem/fn.drop.html
5. Drop 使用场景
对于 Drop 而言,主要有两个功能:
- 回收内存资源
- 执行一些收尾工作
对于第二点,在之前我们已经详细介绍过,因此这里主要对第一点进行下简单说明。
在绝大多数情况下,我们都无需手动去 drop
以回收内存资源,因为 Rust
会自动帮我们完成这些工作,它甚至会对复杂类型的每个字段都单独的调用
drop
进行回收!但是确实有极少数情况,需要你自己来回收资源的,例如文件描述符、网络
socket
等,当这些值超出作用域不再使用时,就需要进行关闭以释放相关的资源,在这些情况下,就需要使用者自己来解决
Drop
的问题。
6. 互斥的 Copy 和 Drop
我们无法为一个类型同时实现 Copy
和 Drop
特征。因为实现了 Copy
特征的类型会被编译器隐式的复制,因此非常难以预测析构函数执行的时间和频率。因此这些实现了
Copy
的类型无法拥有析构函数。
1 |
|
以上代码报错如下:
1 |
|
7. 总结
Drop
可以用于许多方面,来使得资源清理及收尾工作变得方便和安全,甚至可以用其创建我们自己的内存分配器!通过
Drop
特征和 Rust 所有权系统,你无需担心之后的代码清理,Rust
会自动考虑这些问题。
我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保
drop
只会在值不再被使用时被调用一次。