0x00 开篇
截至昨天,关注公众号的小伙伴已经超过1000位了,在这里感谢大家对 Rust 学习的热诚之心。前面已经介绍过一些常用的 trait 了,本篇文章将继续介绍常用的 trait —— Drop。本篇文章的阅读时间大约 8 分钟。
0x01 Drop
在 Rust 中大家现在应该都明确的一个事情:当一个值的所有者离开时,Rust 会清除这个值。大部分情况,不需要我们去手动处理清除值。当然如果你愿意,你也可以通过实现 std::ops::Drop
的 trait 来自定义清除值的方式。来看下官方的定义:
Drop
只有一个方法,不能和 derive 一同使用。使用方法也很简单:
第 11-15 行代码,定义学生结构体 Student
,第 18-23 行为 Student
实现 Drop
。在 drop
方法中打印 Student drop
。代码运行结果:
0x02 Drop 注意事项
只能为结构体(Struct)和枚举(Enum)类型实现 Drop
由于自定义类型需要手动管理的资源,例如堆内存、文件句柄、锁等,需要在值被丢弃时执行清理逻辑。然而在 Rust 中的内置类型,如整数、浮点数、布尔值等,都没有需要手动管理的资源,所以它们并不需要实现 Drop
trait。
不能显式的调用 drop
方法
Rust中,对 drop
方法的隐式调用是调用该方法的唯一方式。但是可以使用 std::mem::drop
函数来调用,只有当函数调用结束后,self
才会被清除。当函数在调用时 self
还没有被清除。示例如下:
第 9 行代码,显式调用 drop
方法是禁止的。第 12 行代码,通过调用 std::mem::drop
函数来释放 s
。再次执行第 14 行代码会发生错误。我们再来看下 std::mem::drop
做了什么?
可以看到,它接受一个类型参数 _x
,但是没有实际操作,看起来像一个空方法。实际上,这个函数使用了 Rust 的所有权机制来自动调用类型 T
的 drop
方法,以释放它占用的资源。
不允许同时实现 Copy 和 Drop trait
当一个类型实现了 Copy
trait 时,它的实例可以按位复制给其他变量,而不会影响原始变量。当一个类型实现了 Drop
trait 时,它需要在值被销毁时执行一些清理工作,比如释放内存等。这两个行为对内存的操作时冲突的,因此不能同时实现。
0x03 Drop 发生的场景
再来详细探讨下哪些场景下会发生 drop。
当值离开作用域
当一个值离开其定义的作用域时,它会被销毁并释放其占用的资源。这是 Rust 中最常见的发生 Drop
的场景,因为 Rust 的所有权模型确保了值离开其作用域时一定会被销毁。前面的示例代码就是这种场景。
当可变变量重新赋值
如果一个变量被重新赋值为另一个值,那么它之前持有的值会被销毁并释放其占用的资源。来看示例代码:
第 6 行代码,创建一个可变变量 s,第 14 行代码,重新赋值,我们在执行第 14 行代码之前,会先 drop
掉之前的值。所以输出结果如下:
表达式的值被 ;
丢弃
如果表达式的值实现了 Drop
trait 类型,则在表达式的结果值被丢弃时,该值的 Drop
实现函数将被调用。来看示例代码:
第 12 行代码执行后,表达式的值被丢弃,所以会在 第 14 行代码 [main end]
打印前先打印第 26 行代码。输出结果如下:
值被传递给函数或者方法中
当一个值被传递给函数或者方法时,它的所有权也会被传递。当函数退出时,这个值会被销毁并释放其占用的资源。如果这个值实现了 Drop
trait,它的 drop
方法会在函数退出时被调用。与前面提到的 std::mem::drop
类似。来看示例代码:
第 12 行代码传入函数 print_student
,所有权转移。在打印第 18 行代码之后离开作用域,会打印第 30 行。输出结果如下:
最后还要再补充一点:当值发生所有权转移时,之前的变量已经变为未初始化的状态。Rust 并不会清除该变量,因为这个变量已经没有值需要被清除了。所以在发生所有权转移时,并不会执行 drop
。
0x04 小结
如果你了解过 C++,那么你会发现,drop
与析构函数很相似。在清除值时,如果该值实现了它的 drop
方法。那么 Rust 会在按常规清除其字段或元素拥有的值之前先调用它的 drop
方法。这种对 drop
方法的隐式调用是调用该方法的唯一方式。