Drop Trait 运行清理代码

ch15-03-drop.md
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

对于智能指针模式来说另一个重要的 trait 是DropDrop运行我们在值要离开作用域时执行一些代码。智能指针在被丢弃时会执行一些重要的清理工作,比如释放内存或减少引用计数。更一般的来讲,数据类型可以管理多于内存的资源,比如文件或网络连接,而使用Drop在代码处理完他们之后释放这些资源。我们在智能指针上下文中讨论Drop是因为其功能几乎总是用于实现智能指针。

在其他一些语言中,我们不得不记住在每次使用完智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定一些代码应该在值离开作用域时被执行,而编译器会自动插入这些代码。这意味着无需记住在所有处理完这些类型实例后调用清理代码,而仍然不会泄露资源!

指定在值离开作用域时应该执行的代码的方式是实现Drop trait。Drop trait 要求我们实现一个叫做drop的方法,它获取一个self的可变引用。

示例 15-8 展示了并没有实际功能的结构体CustomSmartPointer,不过我们会在创建实例之后打印出CustomSmartPointer created.,而在实例离开作用域时打印出Dropping CustomSmartPointer!,这样就能看出每一段代码是何时被执行的。实际的项目中,我们应该在drop中清理任何智能指针运行所需要的资源,而不是这个例子中的println!语句:

文件名: src/main.rs

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer!");
    }
}

fn main() {
    let c = CustomSmartPointer { data: String::from("some data") };
    println!("CustomSmartPointer created.");
    println!("Wait for it...");
}

示例 15-8: 结构体 CustomSmartPointer 实现了 Drop trait, 我们能够放入代码以便在 CustomSmartPointer 离开作用域后进行清理

Drop trait 位于 prelude 中,所以无需导入它。drop方法的实现调用了println!;这里是你需要放入实际关闭套接字代码的地方。在main函数中,我们创建一个CustomSmartPointer的新实例并打印出CustomSmartPointer created.以便在运行时知道代码运行到此处。在main的结尾,CustomSmartPointer的实例会离开作用域。注意我们没有显式调用drop方法:

当运行这个程序,我们会看到:

CustomSmartPointer created.
Wait for it...
Dropping CustomSmartPointer!

被打印到屏幕上,它展示了 Rust 在实例离开作用域时自动调用了drop

可以使用std::mem::drop函数来在值离开作用域之前丢弃它。这通常是不必要的;整个Drop trait 的要点在于它自动的帮我们处理清理工作。在第十六章讲到并发时我们会看到一个需要在离开作用域之前丢弃值的例子。现在知道这是可能的即可,std::mem::drop位于 prelude 中所以可以如示例 15-9 所示直接调用drop

文件名: src/main.rs

fn main() {
    let c = CustomSmartPointer { data: String::from("some data") };
    println!("CustomSmartPointer created.");
    drop(c);
    println!("Wait for it...");
}

示例 15-9: 在一个值离开作用域之前,调用 std::mem::drop 显式进行回收

运行这段代码会打印出如下内容,因为Dropping CustomSmartPointer!CustomSmartPointer created.Wait for it...之间被打印出来,表明析构代码被执行了:

CustomSmartPointer created.
Dropping CustomSmartPointer!
Wait for it...

注意不允许直接调用我们定义的drop方法:如果将示例 15-9 中的drop(c)替换为c.drop(),会得到一个编译错误表明explicit destructor calls not allowed。不允许直接调用Drop::drop的原因是 Rust 在值离开作用域时会自动插入Drop::drop,这样就会丢弃值两次。丢弃一个值两次可能会造成错误或破坏内存,所以 Rust 就不允许这么做。相应的可以调用std::mem::drop,它的定义是:


# #![allow(unused_variables)]
#fn main() {
pub mod std {
    pub mod mem {
        pub fn drop<T>(x: T) { }
    }
}
#}

这个函数对于T是泛型的,所以可以传递任何值。这个函数的函数体并没有任何实际内容,所以它也不会利用其参数。这个空函数的作用在于drop获取其参数的所有权,它意味着在这个函数结尾x离开作用域时x会被丢弃。

使用Drop trait 实现指定的代码在很多方面都使得清理值变得方便和安全:比如可以使用它来创建我们自己的内存分配器!通过Drop trait 和 Rust 所有权系统,就无需担心之后清理代码,因为 Rust 会自动考虑这些问题。如果代码在值仍被使用时就清理它会出现编译错误,因为所有权系统确保了引用总是有效的,这也就保证了drop只会在值不再被使用时被调用一次。

现在我们学习了Box<T>和一些智能指针的特性,让我们聊聊一些其他标准库中定义的拥有各种实用功能的智能指针。