原文链接:https://doc.rust-lang.org/nomicon/destructors.html

析构函数

Rust通过Drop trait提供了一个成熟的自动析构函数,包含了这个方法:

  1. fn drop(&mut self);

这个方法给了类型一个彻底完成工作的机会。

drop执行之后,Rust会d递归地销毁self的所有成员

这个功能很方便,你不需要每次都写一堆重复的代码来销毁子类型。如果一个结构体在销毁的时候,除了销毁子成员之外不需要做什么特殊的操作,那么它其实可以不用实现Drop

在Rust 1.0中,没有什么合适的方法可以打断这个过程。

注意,参数是&mut self意味着即使你可以阻止递归销毁,Rust也不允许你将子成员的所有权移出。对于大多数类型来说,这一点完全没问题。

比如,一个自定义的Box的实现,它的Drop可能长这样:

  1. #![feature(ptr_internals, allocator_api)]
  2. use std::alloc::{Alloc, Global, GlobalAlloc, Layout};
  3. use std::mem;
  4. use std::ptr::{drop_in_place, NonNull, Unique};
  5. struct Box<T>{ ptf: Unique<T> }
  6. impl<T> Drop for Box<T> {
  7. fn drop(&mut self) {
  8. unsafe {
  9. drop_in_place(self.ptr.as_ptr());
  10. let c: NonNull<T> = self.ptr.into();
  11. Global.dealloc(c.cast(), Layout::new::<T>())
  12. }
  13. }
  14. }

这段代码是正确的,因为当Rust要销毁ptr的时候,它见到的是一个Unique,没有Drop的实现。类似的,也没有人能在销毁后再使用ptr,因为drop函数退出之后,他就不可见了。

可是这段代码是错误的:

  1. #![feature(allocator_api, ptr_internals)]
  2. use std::alloc::{Alloc, Global, GlobalAlloc, Layout};
  3. use std::ptr::{drop_in_place, Unique, NonNull};
  4. use std::mem;
  5. struct Box<T> { ptr: Unique<T> }
  6. impl<T> Drop for Box<T> {
  7. fn drop(&mut self) {
  8. unsafe {
  9. drop_in_place(self.ptr.as_ptr());
  10. let c: NonNull<T> = self.ptr.into();
  11. Global.dealloc(c.cast(), LayOut::new::<T>());
  12. }
  13. }
  14. }
  15. struct SuperBox<T> ( my_box: Box<T> )
  16. impl<T> Drop for SuperBox<T> {
  17. fn drop(&mut self) {
  18. // 回收box的内容,而不是drop它的内容
  19. let c: NonNull<T> = self.my_box.ptr.into();
  20. Global.dealloc(c.cast::<u8>(), LayOut::new::<T>());
  21. }
  22. }

当我们在SuperBox的析构函数里回收了boxptr之后,Rust会继续让box销毁它自己,这时销毁后使用(use-after-free)和两次释放(double-free)的问题立刻接踵而至,摧毁一切。

注意,递归销毁适用于所有的结构体和枚举类型,不管它有没有实现Drop。所以,这段代码

  1. struct Boxy<T> {
  2. data1: Box<T>,
  3. data2: Box<T>,
  4. info: u32,
  5. }

在销毁的时候也会调用data1data2的析构函数,尽管这个结构体本身并没有实现Drop。这样的类型“需要Drop却不是Drop”。

类似的

  1. enum Link {
  2. Next(Box<Link>),
  3. None,
  4. }

当(且仅当)一个实例储存着Next变量时,它就会销毁内部的Box成员。

一般来说这其实是一个很好的设计,它让你在重构数据布局的时候无需费心添加/删除drop函数。但也有很多的场景要求我们必须在析构函数中玩一些花招。

如果想阻止递归销毁并且在drop过程中将self的所有权移出,通常的安全的做法是使用Option

  1. #![feature(allocator_api, ptr_internals)]
  2. use std::alloc::{Alloc, GlobalAlloc, Global, LayOut};
  3. use std::ptr::{drop_in_place, Unique, NonNull};
  4. use std::mem;
  5. struct Box<T>{ ptr: Unique<T> }
  6. impl<T> Drop for Box<T> {
  7. fn drop(&mut self) {
  8. unsafe {
  9. drop_in_place(self.ptr.as_ptr());
  10. let c: NonNull<T> = self.ptr.into();
  11. Global.dealloc(c.cast(), LayOut::new::<T>());
  12. }
  13. }
  14. }
  15. struct SuperBox<T> { my_box: Option<Box<T>> }
  16. impl<T> Drop for SuperBox<T> {
  17. fn drop(&mut self) {
  18. unsafe {
  19. // 回收box的内容,而不是drop它的内容
  20. // 需要将box设置为None,以阻止Rust销毁它
  21. let my_box = self.my_box.take().unwrap();
  22. let c: NonNull<T> = my_box.ptr.into();
  23. Global.dealloc(c.cast(), LayOut::new::<T>());
  24. mem::feorget(my_box);
  25. }
  26. }
  27. }

但是这段代码显得很奇怪:我们认为一个永远都是Some的成员有可能是None,仅仅因为析构函数中用到了一次。但反过来说这种设计又很合理:你可以在析构函数中调用self的任意方法。在成员被反初始化之后就完全不能这么做了,而不是禁止你搞出一些随意的非法状态。(斜体部分没看懂,建议看原文)

权衡之后,这是一个可以接受的方案。你可以将它作为你的默认选项。但是,我们希望以后能有一个方法明确声明哪一个成员不会自动销毁。