Rust 中级教程 第21课——Drop trait

418 阅读4分钟

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 方法的隐式调用是调用该方法的唯一方式