析构函数
该语言确实通过 Drop
trait 提供了完整的自动析构函数,该 trait 提供了以下方法
fn drop(&mut self);
此方法为类型提供时间以某种方式完成它正在执行的操作。
运行 drop
后,Rust 将递归尝试删除 self
的所有字段。
这是一项便利功能,因此您不必编写“析构函数样板”来删除子级。如果一个结构体除了删除其子级之外没有其他特殊的删除逻辑,则意味着根本不需要实现 Drop
!
在 Rust 1.0 中,没有稳定的方法可以阻止此行为。
请注意,获取 &mut self
意味着即使您可以抑制递归 Drop,Rust 也会阻止您例如将字段移出 self。对于大多数类型,这完全没问题。
例如,Box
的自定义实现可能会像这样编写 Drop
#![feature(ptr_internals, allocator_api)] use std::alloc::{Allocator, Global, GlobalAlloc, Layout}; use std::mem; use std::ptr::{drop_in_place, NonNull, Unique}; struct Box<T>{ ptr: Unique<T> } impl<T> Drop for Box<T> { fn drop(&mut self) { unsafe { drop_in_place(self.ptr.as_ptr()); let c: NonNull<T> = self.ptr.into(); Global.deallocate(c.cast(), Layout::new::<T>()) } } } fn main() {}
这可以正常工作,因为当 Rust 删除 ptr
字段时,它只会看到一个没有实际 Drop
实现的 Unique。同样,在释放后,没有任何东西可以使用 ptr
,因为当 drop 退出时,它将变得不可访问。
但是,这将不起作用
#![feature(allocator_api, ptr_internals)] use std::alloc::{Allocator, Global, GlobalAlloc, Layout}; use std::ptr::{drop_in_place, Unique, NonNull}; use std::mem; struct Box<T>{ ptr: Unique<T> } impl<T> Drop for Box<T> { fn drop(&mut self) { unsafe { drop_in_place(self.ptr.as_ptr()); let c: NonNull<T> = self.ptr.into(); Global.deallocate(c.cast(), Layout::new::<T>()); } } } struct SuperBox<T> { my_box: Box<T> } impl<T> Drop for SuperBox<T> { fn drop(&mut self) { unsafe { // Hyper-optimized: deallocate the box's contents for it // without `drop`ing the contents let c: NonNull<T> = self.my_box.ptr.into(); Global.deallocate(c.cast::<u8>(), Layout::new::<T>()); } } } fn main() {}
在我们释放 SuperBox 的析构函数中的 box
的 ptr 后,Rust 将很乐意继续告诉 box 删除自身,并且所有内容都将因释放后使用和重复释放而崩溃。
请注意,递归删除行为适用于所有结构体和枚举,而不管它们是否实现了 Drop。因此,类似于
#![allow(unused)] fn main() { struct Boxy<T> { data1: Box<T>, data2: Box<T>, info: u32, } }
的内容将在其“将要”被删除时删除其 data1 和 data2 的字段析构函数,即使它本身没有实现 Drop。我们说这样的类型需要 Drop,即使它本身不是 Drop。
同样地,
#![allow(unused)] fn main() { enum Link { Next(Box<Link>), None, } }
的内容将仅当实例存储 Next 变体时才会删除其内部 Box 字段。
总的来说,这非常有效,因为您无需在重构数据布局时担心添加/删除 drop。当然,在需要使用析构函数执行更复杂的操作时,肯定有许多有效的用例。
覆盖递归删除并在 drop
期间允许移出 Self 的经典安全解决方案是使用 Option
#![feature(allocator_api, ptr_internals)] use std::alloc::{Allocator, GlobalAlloc, Global, Layout}; use std::ptr::{drop_in_place, Unique, NonNull}; use std::mem; struct Box<T>{ ptr: Unique<T> } impl<T> Drop for Box<T> { fn drop(&mut self) { unsafe { drop_in_place(self.ptr.as_ptr()); let c: NonNull<T> = self.ptr.into(); Global.deallocate(c.cast(), Layout::new::<T>()); } } } struct SuperBox<T> { my_box: Option<Box<T>> } impl<T> Drop for SuperBox<T> { fn drop(&mut self) { unsafe { // Hyper-optimized: deallocate the box's contents for it // without `drop`ing the contents. Need to set the `box` // field as `None` to prevent Rust from trying to Drop it. let my_box = self.my_box.take().unwrap(); let c: NonNull<T> = my_box.ptr.into(); Global.deallocate(c.cast(), Layout::new::<T>()); mem::forget(my_box); } } } fn main() {}
但是,这具有一些相当奇怪的语义:您是在说一个应该始终为 Some 的字段可能为 None,仅仅是因为这发生在析构函数中。当然,反过来这也很有意义:您可以在析构函数期间对 self 调用任意方法,这应该可以防止您在取消初始化字段后这样做。并不是说它会阻止您在其中产生任何其他任意无效状态。
总的来说,这是一个不错的选择。当然,您应该默认使用它。但是,我们希望将来会有一种一流的方式来声明不应自动删除字段。