使用 Drop 特征在清理时运行代码


对智能指针模式很重要的第二个特征是 Drop,它允许您自定义当值即将超出范围时发生的情况。您可以为任何类型的 Drop trait 提供实现,并且该代码可用于释放文件或网络连接等资源。


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


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


您可以通过实现 Drop 特征。Drop trait 要求您实现一个名为 drop 的 SETself 的可变引用。要查看 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,并为调用 println!drop 方法提供了一个实现。主体 drop 函数是放置要运行的任何 logic 的位置,当类型的实例超出范围时。我们在这里打印了一些文本,以直观地演示 Rust 何时会调用 drop


main 中,我们创建两个 CustomSmartPointer 实例,然后打印 已创建 CustomSmartPointers。在 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,调用我们指定的代码。变量的创建顺序是按相反的顺序删除的,因此 dc 之前被删除。此示例的目的是为您提供有关 drop 方法如何工作的直观指南;通常,您将指定您的类型需要运行的清理代码,而不是打印消息。


使用 std::mem::d rop 提前删除值


不幸的是,禁用自动丢弃并不简单 功能性。禁用 drop 通常不是必需的;的重点 Drop trait 是它会自动处理。但是,有时您可能希望尽早清理值。一个示例是使用管理锁的智能指针时:您可能希望强制释放锁的 drop 方法,以便同一范围内的其他代码可以获取锁。Rust 不允许你手动调用 Drop trait 的 drop 方法;相反,如果要强制在其范围结束之前删除值,则必须调用标准库提供的 std::mem::d rop 函数。


如果我们尝试通过修改 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。错误消息使用术语 destructor,这是用于清理实例的函数的通用编程术语。析构函数类似于 constructor 创建实例。Rust 中的 drop 函数是一个特定的析构函数。


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


当值超出范围时,我们不能禁用 drop 的自动插入,也不能显式调用 drop 方法。因此,如果我们需要强制提前清理一个值,我们使用 std::mem::d rop 函数。


std::mem::d rop 函数与 Drop 中的 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::d rop 在值超出范围之前显式删除值


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

$ 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.CustomSmartPointer dropped before the end of main. text 之间,表明在该点调用 drop 方法代码以放置 C


您可以通过多种方式使用 Drop trait 实现中指定的代码来方便和安全:例如,您可以使用它来创建自己的内存分配器!使用 Drop trait 和 Rust 的所有权系统,你不必记得清理,因为 Rust 会自动进行清理。


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


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