写点什么

Rust 从 0 到 1- 智能指针 -Drop trait

用户头像
关注
发布于: 3 小时前
Rust从0到1-智能指针-Drop trait

对于智能指针来说第二个重要的 trait 是 Drop,它让我们可以在变量离开其作用域时自定义一些行为。我们可以为任何类型实现 Drop trait,一般我们会在其中对使用的资源进行释放,譬如,文件句柄或网络连接。我们在介绍智能指针时对 Drop trait 进行讨论,是因为在创建智能指针类型时,我们几乎总会实现它。譬如,当 Box<T> 类型的变量离开作用域被丢弃时,会释放其所指向的堆空间。

在一些其它语言中,我们不得不在每次使用完智能指针后手动清理内存或资源。如果忘记的话,则系统的资源可能会被耗尽并崩溃。在 Rust 中,我们可以自定义在变量离开作用域时需要执行的代码,编译器会自动“插入”这些代码。这样我们就不需要小心翼翼的在程序中到处编写或调用这些清理资源的代码 —— 避免了资源的泄露。

Drop trait 要求实现一个 drop 方法,它的参数是一个指向 self 的可变引用。下面让我们通过 println! 代替释放资源的操作来体验下:

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.");}
复制代码

由于 Drop trait 默认包含在 prelude 中,因此可以直接使用,不需要再导入。我们为 CustomSmartPointer 实现了 Drop trait,即在 drop 方法中调用 println! 进行打印。它最终会在 CustomSmartPointer  类型的实例离开作用域时打印一段文本。注意,我们并没有显示的调用 drop 方法。尝试运行上面的例子,我们会得到类似下面的输出结果:

$ cargo run   Compiling drop-example v0.1.0 (file:///projects/drop-example)    Finished dev [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 方法。变量以创建时相反的顺序被丢弃,所以 d 的 drop 方法在 c 之前被调用。这个例子给了我们一个比较直观的感受,不过通常我们会在 drop 方法执行必要的清理工作而不是简单的打印信息。

主动 Drop

Rust 并不允许我们对自动执行 drop 进行禁用,通常我们也不需要这么做,而且 Drop trait 很重要的一点就是会自动的执行。但是,在有些场景下我们可能确实需要提前对某些资源进行清理,而不是等到离开作用域时。譬如,当使用智能指针来管理锁时;我们可能会希望主动调用 drop 方法来释放锁以便作用域中的其它代码可以获得锁。那么这时候怎么办呢?当我们希望在作用域结束之前就释放资源的话,我们需要使用由标准库提供的 std::mem::drop。如果我们像下面的例子这样主动调用 drop 方法:

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

我们会得到类似下面的编译错误:

$ 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: `drop(c)`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0040`.error: could not compile `drop-example`
To learn more, run the command again with --verbose.
复制代码

编译器提示我们不允许调用 drop。这里使用了一个术语“析构函数”(destructor),这是一个通用的编程术语,指用于清理占用的资源的函数,与用于创建实例的构造函数相对应。Rust 中的 drop 函数就属于析构函数的概念。

因为 Rust 会在作用域结束时自动调用 drop 函数,所以 Rust 不允许我们手动调用 drop ,这会导致“双重释放”(double free)的错误,即尝试清理同一个数据两次。

我们既不能禁止 Rust 自动调用 drop,也不能手动调用 drop。如果我们需要提早进行清理,可以使用 std::mem::drop 函数,其参数就是希望提早丢弃的变量。std::mem::drop 也已默认包含于 prelude,所以可以直接调用,参考下面的例子:

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

尝试运行这段代码,我们会得到类似下面的结果:

$ cargo run   Compiling drop-example v0.1.0 (file:///projects/drop-example)    Finished dev [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. 之间,这表明 c 在离开作用域之前被提前丢弃了,并调用了其 drop 方法。

我们可以在很多场景下通过实现 Drop trait 来使得清理工作变得方便和安全:譬如,我们可以创建自己的内存分配器,然后利用 Drop trait 和 Rust 的所有权机制,我们无需每次都记着清理工作,Rust 会自动完成;我们也无需担心不小心清理掉了仍在使用的数据:所有权机制会确保引用总是有效的,同时也会确保在变量被丢弃时, drop 只会被调用一次。

发布于: 3 小时前阅读数: 3
用户头像

关注

公众号"山 顽石"欢迎大家关注;) 2021.03.23 加入

IT老兵,关注Rust、Java、前端、架构、大数据、AI、算法等;平时喜欢美食、旅游、宠物、健身、篮球、乒乓球等。希望能和大家交流分享。

评论

发布
暂无评论
Rust从0到1-智能指针-Drop trait