使用 Drop
特质运行清理代码
Running Code on Cleanup with Drop
Trait
对于灵巧指针模式来讲,第二个重要的特质是 Drop
,他允许咱们定制某个值即将超出作用域时发生的事情。咱们可以在任何类型上,为 Drop
特质提供一个实现,实现代码可用于释放文件或网络连接等资源。
咱们之所以在灵巧指针上下文中引入 Drop
特质,是由于 Drop
特质的功能在实现某个灵巧指针时几乎都会用到。比如,当 Box<T>
被弃用时,他将释放该匣子指向的堆上的内存空间。
在某些语言中,对于某些类型,程序员必须在每次使用完这些类型的实例后,调用代码来释放内存或资源。这方面的例子包括文件句柄、套接字或锁,file handles, sockets, or locks。如果他们忘记了,系统可能会变得过载并崩溃。在 Rust 中,咱们可以指定在值超出范围时运行一段特定的代码,编译器将自动插入这段代码。因此,咱们无需小心地将清理代码,放在程序中某个特定类型的实例完成后的任何地方 -- 咱们仍然不会泄露资源!
咱们通过实现 Drop
特质,来指定当一个值超出作用域时要运行的代码。Drop
特质要求咱们实现一个名为 drop
的方法,他需要一个对 self
的可变引用。为了了解Rust 何时调用 drop
,现在咱们就来用 println!
语句实现 drop
。
下面清单 15-14 给出了仅有着一项定制功能,即在其实例超出作用域时打印出 正在弃用 CustomSmartPointer!
的一个 CumstomSmartPointer
结构体,以展示出 Rust 在何时运行这个 drop
函数。
文件名:src/main.rs
struct CustomSmartPointer { data: String, } impl Drop for CustomSmartPointer { fn drop(&mut self) { println! ("正在使用数据 `{}` 弃用 CustomSmartPointer!", self.data); } } fn main() { let c = CustomSmartPointer { data: String::from("c - 我的事情"), }; let d = CustomSmartPointer { data: String::from("d - 其他事情"), }; println! ("已创建出一些 CustomSmartPointer 实例"); }
清单 15-14:一个实现了 Drop
特质的 CustomSmartPointer
结构体,咱们将把咱们的清理代码放在这里
Drop
特质包含在前奏中,included in the prelude,所以我们不需要把他带入作用域。我们在 CustomSmartPointer
上实现了 Drop
特质,并为 drop
方法提供到一个调用了 println!
的实现。drop
函数的主体是在咱们的类型的实例超出作用域时,打算运行的任何逻辑的地方。咱们在这里打印一些文本来直观地演示 Rust 何时调用 drop
。
在 main
中,我们创建了两个 CustomSmartPointer
的实例,然后打印已创建出一些 CumstomSmartPointer 实例
。在 main
的结尾,我们的 CustomSmartPointer
实例将超出作用域,Rust 将调用我们放在 drop
方法中的代码,打印我们的最终信息。注意,我们不需要显式地调用 drop
方法。
当我们运行这个程序时,我们会看到以下输出:
$ cargo run
Compiling sp_demos v0.1.0 (/home/lennyp/rust-lang/sp_demos)
Finished dev [unoptimized + debuginfo] target(s) in 0.50s
Running `target/debug/sp_demos`
已创建出一些 CustomSmartPointer 实例
正在使用数据 `d - 其他事情` 弃用 CustomSmartPointer!
正在使用数据 `c - 我的事情` 弃用 CustomSmartPointer!
当我们的实例超出作用域时,Rust 自动为我们调用了 drop
,从而调用我们指定的代码。变量的弃用顺序与其创建顺序相反,因此 d
在 c
之前被弃用。这个例子的目的是给咱们一个直观了解 drop
方法如何工作的直观指引;通常咱们会指定咱们类型需要运行的清理代码,而不是打印消息。
使用 std::mem::drop
提前弃用值
Drop a Value Early with std::mem::drop
不幸的是,要禁用自动 drop
功能并不简单。通常情况下,禁用 drop
功能是没有必要的;Drop
特质的全部意义在于他是自动处理的。然而在少数情况下,咱们可能想要提前清理一个值。一个例子便是在运用管理锁的灵巧指针时:咱们可能想要强制使用释放锁的 drop
方法,这样同一作用域内的其他代码就可以获得锁。Rust 不允许咱们手动调用 Drop
特质的 drop
方法;相反,如果咱们打算强制一个值在其作用域结束前被弃用,咱们必须调用标准库提供的 std::mem::drop
函数。
若我们试图通过修改清单 15-14 中的 main
函数来手动调用 Drop
特质的 drop
方法,如清单15-15所示,我们会得到一个编译器报错:
文件名:src/main.rs
fn main() { let c = CustomSmartPointer { data: String::from("一些数据"), }; println! ("已创建出一个 CustomSmartPointer 实例。"); c.drop(); println! ("在 main 结束之前这个 CustomSmartPointer 已被弃用。") }
清单 15-15:尝试调用 Drop
特质的 drop
方法来提前清理
当我们试图编译这段代码时,我们会得到这样的报错:
$ cargo run
Compiling sp_demos v0.1.0 (/home/lennyp/rust-lang/sp_demos)
error[E0040]: explicit use of destructor method
--> src/main.rs:17:7
|
17 | c.drop();
| --^^^^--
| | |
| | explicit destructor calls not allowed
| help: consider using `drop` function: `drop(c)`
For more information about this error, try `rustc --explain E0040`.
error: could not compile `sp_demos` due to previous error
这个错误信息指出,我们不允许显式调用 drop
。这条错误信息使用了术语 “解构函数,destructor”,这是清理实例的函数的通用编程术语。解构函数类似于 构造函数,constructor,后者创建一个实例。Rust 中的 drop
函数就是一个特殊的解构函数。
Rust 之所以不允许咱们显式地调用 drop
,是因为 Rust 仍然会在 main
函数结尾处自动调用值上的 drop
,这将导致 双重释放,double free 的错误,由于 Rust 会试图对同一个值进行两次清理。
当值超出作用域时,我们无法禁用 drop
的自动插入,也无法显式调用 drop
方法。所以,如果我们需要强制一个值提前被清理,我们就使用 std::mem::drop
函数。
std::mem::drop
函数与 Drop
特质中的 drop
方法不同。咱们通过把咱们想要强制弃用的值作为参数传递来调用他。这个函数在前奏中,所以我们可以修改清单 15-15 中的 main
来调用 drop
函数,如清单 15-16 所示:
文件名:src/main.rs
fn main() { let c = CustomSmartPointer { data: String::from("我的事情"), }; println! ("已创建出一个 CustomSmartPointer 实例。"); drop(c); println! ("在 main 结束之前这个 CustomSmartPointer 已被弃用。") }
清单 15-16:调用 std::mem::drop
在值超出作用域前,显式地弃用该值
运行这段代码将打印出以下内容:
$ cargo run
Compiling sp_demos v0.1.0 (/home/lennyp/rust-lang/sp_demos)
Finished dev [unoptimized + debuginfo] target(s) in 0.40s
Running `target/debug/sp_demos`
已创建出一个 CustomSmartPointer 实例。
正在使用数据 `一些数据` 弃用 CustomSmartPointer!
在 main 结束之前这个 CustomSmartPointer 已被弃用。
文本 正在使用数据 `一些数据` 弃用 CustomSmartPointer!
被打印在 已创建出一个 CustomSmartPointer 实例。
与 在 main 结束之前这个 CustomSmartPointer 已被弃用。
之间,显示 drop
方法在这个时间点被调用来弃用 c
。
咱们可以通过多种方式使用 Drop
特质实现中指定的代码,来方便和安全地进行清理:例如,咱们可以用他来创建咱们自己的内存分配器! 有了 Drop
特质和 Rust 的所有权系统,咱们不需要记得清理内存,因为 Rust 会自动完成。
咱们也不必担心因意外清理仍在使用的值而导致的问题:确保引用始终有效的所有权系统,还确保在值不再被使用时,drop
只被调用一次。
现在我们已经研究了 Box<T>
和灵巧指针的一些特性,让我们看看标准库中定义的其他几个智能指针。