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

对于智能指针模式来说,第二个重要的特性是Drop,它允许你自定义当值即将离开作用域时发生的事情。你可以为任何类型提供Drop特性的实现,这段代码可以用来释放资源,如文件或网络连接。

我们在智能指针的上下文中介绍Drop,因为Drop特性的功能几乎总是在实现智能指针时使用。例如,当Box<T>被丢弃时,它会释放该box指向的堆上的空间。

在某些语言中,对于某些类型,程序员必须在每次使用完这些类型的实例后调用代码来释放内存或资源。例如文件句柄、套接字和锁。如果他们忘记了,系统可能会过载并崩溃。在Rust中,你可以指定当值离开作用域时运行特定的代码,编译器会自动插入这段代码。因此,你不需要小心地在程序中的每个地方放置清理代码,即使特定类型的实例已经完成——你仍然不会泄漏资源!

你通过实现Drop特性来指定当值离开作用域时要运行的代码。Drop特性要求你实现一个名为drop的方法,该方法接受一个对self的可变引用。为了看看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:实现了Drop特性的CustomSmartPointer结构体,我们会在其中放置清理代码

Drop特性包含在预导入(prelude)中,所以我们不需要将其引入作用域。我们在CustomSmartPointer上实现Drop特性,并为调用println!drop方法提供实现。drop方法的主体是你放置任何你希望在类型实例离开作用域时运行的逻辑的地方。我们在这里打印一些文本,以直观地演示Rust何时调用drop

main中,我们创建了两个CustomSmartPointer实例,然后打印CustomSmartPointers created。在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方法如何工作的直观指南;通常你会指定你的类型需要运行的清理代码,而不是打印消息。

不幸的是,禁用自动drop功能并不简单。禁用drop通常是不必要的;Drop特性的全部要点是它会被自动处理。然而,有时你可能想要提前清理一个值。一个例子是当使用管理锁的智能指针时:你可能想要强制调用释放锁的drop方法,这样同一作用域中的其他代码就可以获取锁。Rust不允许你手动调用Drop特性的drop方法;相反,如果你想强制一个值在其作用域结束前被丢弃,你必须调用标准库提供的std::mem::drop函数。

如果我们尝试通过修改示例15-14中的main函数来手动调用Drop特性的drop方法,如示例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特性的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。错误消息使用了析构函数这个术语,这是一个通用的编程术语,指的是清理实例的函数。析构函数类似于构造函数,后者创建一个实例。Rust中的drop函数是一种特定的析构函数。

Rust不允许我们显式调用drop,因为Rust仍然会在main结束时自动调用值上的drop。这会导致双重释放错误,因为Rust会尝试两次清理同一个值。

我们不能禁用当值离开作用域时自动插入drop,也不能显式调用drop方法。因此,如果我们需要强制提前清理一个值,我们使用std::mem::drop函数。

std::mem::drop函数与Drop特性中的drop方法不同。我们通过传递我们想要强制丢弃的值作为参数来调用它。该函数在预导入中,所以我们可以修改示例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::drop在值离开作用域前显式丢弃它

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

$ 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 created.和CustomSmartPointer dropped before the end of main.文本之间,表明drop方法代码被调用以在那一点丢弃c

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

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

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