通过 Drop Trait 在清理时运行代码

对于智能指针模式而言,第二个重要的 trait 是 Drop,它允许你自定义当值即将离开作用域时会发生什么。你可以为任何类型提供 Drop trait 的实现,并且该代码可以用于释放资源,例如文件或网络连接。

我们在智能指针的上下文中介绍 Drop,是因为 Drop trait 的功能几乎总是在实现智能指针时使用。例如,当 Box<T> 被 drop 时,它将释放 box 指向的堆上的空间。

在某些语言中,对于某些类型,程序员每次完成使用这些类型的实例时都必须调用代码来释放内存或资源。示例包括文件句柄、套接字或锁。如果他们忘记了,系统可能会变得过载并崩溃。在 Rust 中,你可以指定一段特定的代码在值超出作用域时运行,并且编译器将自动插入此代码。因此,你无需小心地在程序中放置清理代码,以处理特定类型的实例何时完成使用 —— 你仍然不会泄漏资源!

你可以通过实现 Drop trait 来指定在值超出作用域时运行的代码。Drop trait 要求你实现一个名为 drop 的方法,该方法接受对 self 的可变引用。为了了解 Rust 何时调用 drop,我们现在使用 println! 语句来实现 drop

列表 15-14 展示了一个 CustomSmartPointer 结构体,其唯一的自定义功能是,当实例超出作用域时,它将打印 Dropping CustomSmartPointer!,以显示 Rust 何时运行 drop 函数。

文件名: src/main.rs

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}

列表 15-14: 一个 CustomSmartPointer 结构体,它实现了 Drop trait,我们将在其中放置清理代码

Drop trait 包含在 prelude 中,因此我们不需要将其引入作用域。我们在 CustomSmartPointer 上实现了 Drop trait,并为 drop 方法提供了实现,该实现调用 println!drop 函数的主体是你放置任何你希望在类型实例超出作用域时运行的逻辑的地方。我们在此处打印一些文本,以直观地演示 Rust 何时将调用 drop

main 中,我们创建了 CustomSmartPointer 的两个实例,然后打印 CustomSmartPointers created。在 main 的末尾,我们的 CustomSmartPointer 实例将超出作用域,Rust 将调用我们放在 drop 方法中的代码,并打印我们的最终消息。请注意,我们不需要显式调用 drop 方法。

当我们运行此程序时,我们将看到以下输出

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running `target/debug/drop-example`
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

当我们的实例超出作用域时,Rust 自动为我们调用了 drop,调用了我们指定的代码。变量以与其创建顺序相反的顺序 drop,因此 dc 之前被 drop。此示例的目的是为你提供 drop 方法如何工作的可视化指南;通常,你将指定你的类型需要运行的清理代码,而不是打印消息。

使用 std::mem::drop 提前 Drop 值

不幸的是,禁用自动 drop 功能并不简单。通常不需要禁用 dropDrop trait 的全部意义在于它是自动处理的。但是,有时你可能希望提前清理值。一个示例是使用管理锁的智能指针时:你可能希望强制执行释放锁的 drop 方法,以便同一作用域中的其他代码可以获取该锁。Rust 不允许你手动调用 Drop trait 的 drop 方法;相反,如果你想强制在值超出其作用域之前将其 drop,则必须调用标准库提供的 std::mem::drop 函数。

如果我们尝试手动调用 Drop trait 的 drop 方法,方法是修改列表 15-14 中的 main 函数,如列表 15-15 所示,我们将收到编译器错误

文件名: src/main.rs

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    c.drop();
    println!("CustomSmartPointer dropped before the end of main.");
}

列表 15-15: 尝试手动从 Drop trait 调用 drop 方法以提前清理

当我们尝试编译此代码时,我们将收到此错误

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
error[E0040]: explicit use of destructor method
  --> src/main.rs:16:7
   |
16 |     c.drop();
   |       ^^^^ explicit destructor calls not allowed
   |
help: consider using `drop` function
   |
16 |     drop(c);
   |     +++++ ~

For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` (bin "drop-example") due to 1 previous error

此错误消息指出我们不允许显式调用 drop。错误消息使用了术语析构函数,它是用于清理实例的函数的通用编程术语。析构函数类似于构造函数,后者创建实例。Rust 中的 drop 函数是一个特定的析构函数。

Rust 不允许我们显式调用 drop,因为 Rust 仍然会在 main 末尾自动调用值的 drop。这将导致double free错误,因为 Rust 将尝试清理同一个值两次。

我们无法禁用在值超出作用域时自动插入 drop,也无法显式调用 drop 方法。因此,如果我们需要强制提前清理值,我们使用 std::mem::drop 函数。

std::mem::drop 函数与 Drop trait 中的 drop 方法不同。我们通过传递我们想要强制 drop 的值作为参数来调用它。该函数在 prelude 中,因此我们可以修改列表 15-15 中的 main 以调用 drop 函数,如列表 15-16 所示

文件名: src/main.rs

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    drop(c);
    println!("CustomSmartPointer dropped before the end of main.");
}

列表 15-16: 调用 std::mem::drop 以在值超出作用域之前显式 drop 它

运行此代码将打印以下内容

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
     Running `target/debug/drop-example`
CustomSmartPointer created.
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main.

文本 Dropping CustomSmartPointer with data `some data`! 打印在 CustomSmartPointer created.CustomSmartPointer dropped before the end of main. 文本之间,表明 drop 方法代码在此时被调用以 drop c

你可以使用 Drop trait 实现中指定的代码,以多种方式使清理变得方便和安全:例如,你可以使用它来创建自己的内存分配器!借助 Drop trait 和 Rust 的所有权系统,你无需记住清理,因为 Rust 会自动执行此操作。

你也不必担心因意外清理仍在使用的值而导致的问题:确保引用始终有效的所有权系统也确保 drop 仅在值不再使用时调用一次。

现在我们已经检查了 Box<T> 和智能指针的一些特性,让我们看一下标准库中定义的一些其他智能指针。