析构函数

该语言确实通过 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 调用任意方法,这应该可以防止您在取消初始化字段后这样做。并不是说它会阻止您在其中产生任何其他任意无效状态。

总的来说,这是一个不错的选择。当然,您应该默认使用它。但是,我们希望将来会有一种一流的方式来声明不应自动删除字段。