You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trpl-zh-cn/src/ch15-03-drop.md

86 lines
6.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## 使用 `Drop` Trait 运行清理代码
> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/main/src/ch15-03-drop.md)
> <br>
> commit 5a3a64d60b0dd786c35ca4daada7a4d20da33e5e
对于智能指针模式来说第二个重要的 trait 是 `Drop`,其允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。
我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,当 `Box<T>` 被丢弃时会释放 box 指向的堆空间。
在其他一些语言中的某些类型,我们不得不记住在每次使用完那些类型的智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定每当值离开作用域时被执行的代码,编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码 —— 而且还不会泄漏资源。
指定在值离开作用域时应该执行的代码的方式是实现 `Drop` trait。`Drop` trait 要求实现一个叫做 `drop` 的方法,它获取一个 `self` 的可变引用。为了能够看出 Rust 何时调用 `drop`,让我们暂时使用 `println!` 语句实现 `drop`
示例 15-14 展示了唯一定制功能就是当其实例离开作用域时,打印出 `Dropping CustomSmartPointer!` 的结构体 `CustomSmartPointer`,这会演示 Rust 何时运行 `drop` 函数:
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-14/src/main.rs}}
```
<span class="caption">示例 15-14结构体 `CustomSmartPointer`,其实现了放置清理代码的 `Drop` trait</span>
`Drop` trait 包含在 prelude 中,所以无需导入它。我们在 `CustomSmartPointer` 上实现了 `Drop` trait并提供了一个调用 `println!``drop` 方法实现。`drop` 函数体是放置任何当类型实例离开作用域时期望运行的逻辑的地方。这里选择打印一些文本以可视化地展示 Rust 何时调用 `drop`
`main` 中,我们新建了两个 `CustomSmartPointer` 实例并打印出了 `CustomSmartPointer created.`。在 `main` 的结尾,`CustomSmartPointer` 的实例会离开作用域,而 Rust 会调用放置于 `drop` 方法中的代码,打印出最后的信息。注意无需显式调用 `drop` 方法:
当运行这个程序,会出现如下输出:
```console
{{#include ../listings/ch15-smart-pointers/listing-15-14/output.txt}}
```
当实例离开作用域 Rust 会自动调用 `drop`,并调用我们指定的代码。变量以被创建时相反的顺序被丢弃,所以 `d``c` 之前被丢弃。这个例子的作用是给了我们一个 drop 方法如何工作的可视化指导,不过通常需要指定类型所需执行的清理代码而不是打印信息。
#### 通过 `std::mem::drop` 提早丢弃值
不幸的是,我们并不能直截了当的禁用 `drop` 这个功能。通常也不需要禁用 `drop` ;整个 `Drop` trait 存在的意义在于其是自动处理的。然而,有时你可能需要提早清理某个值。一个例子是当使用智能指针管理锁时;你可能希望强制运行 `drop` 方法来释放锁以便作用域中的其他代码可以获取锁。Rust 并不允许我们主动调用 `Drop` trait 的 `drop` 方法;当我们希望在作用域结束之前就强制释放变量的话,我们应该使用的是由标准库提供的 `std::mem::drop`
如果我们像是示例 15-14 那样尝试调用 `Drop` trait 的 `drop` 方法,就会得到像示例 15-15 那样的编译错误:
<span class="filename">文件名src/main.rs</span>
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-15/src/main.rs:here}}
```
<span class="caption">示例 15-15尝试手动调用 `Drop` trait 的 `drop` 方法提早清理</span>
如果尝试编译代码会得到如下错误:
```console
{{#include ../listings/ch15-smart-pointers/listing-15-15/output.txt}}
```
错误信息表明不允许显式调用 `drop`。错误信息使用了术语 **析构函数**_destructor_这是一个清理实例的函数的通用编程概念。**析构函数** 对应创建实例的 **构造函数**。Rust 中的 `drop` 函数就是这么一个析构函数。
Rust 不允许我们显式调用 `drop` 因为 Rust 仍然会在 `main` 的结尾对值自动调用 `drop`,这会导致一个 *double free* 错误,因为 Rust 会尝试清理相同的值两次。
因为不能禁用当值离开作用域时自动插入的 `drop`,并且不能显式调用 `drop`,如果我们需要强制提早清理值,可以使用 `std::mem::drop` 函数。
`std::mem::drop` 函数不同于 `Drop` trait 中的 `drop` 方法。可以通过传递希望强制丢弃的值作为参数。`std::mem::drop` 位于 prelude所以我们可以修改示例 15-15 中的 `main` 来调用 `drop` 函数。如示例 15-16 所示:
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-16/src/main.rs:here}}
```
<span class="caption">示例 15-16: 在值离开作用域之前调用 `std::mem::drop` 显式清理</span>
运行这段代码会打印出如下:
```console
{{#include ../listings/ch15-smart-pointers/listing-15-16/output.txt}}
```
`` Dropping CustomSmartPointer with data `some data`! `` 出现在 `CustomSmartPointer created.``CustomSmartPointer dropped before the end of main.` 之间,表明了 `drop` 方法被调用了并在此丢弃了 `c`
`Drop` trait 实现中指定的代码可以用于许多方面,来使得清理变得方便和安全:比如可以用其创建我们自己的内存分配器!通过 `Drop` trait 和 Rust 所有权系统你无需担心之后的代码清理Rust 会自动考虑这些问题。
我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保 `drop` 只会在值不再被使用时被调用一次。
现在我们学习了 `Box<T>` 和一些智能指针的特性,让我们聊聊标准库中定义的其他几种智能指针。