键盘快捷键

按 ← 或 → 在章节之间导航

按 S 或 / 在本书中搜索

按 ? 显示此帮助

按 Esc 隐藏此帮助

使用 Drop Trait 在清理时运行代码

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

我们在智能指针的背景下介绍 Drop,因为 Drop trait 的功能在实现智能指针时几乎总是会被用到。例如,当一个 Box<T> 被丢弃时,它会释放 box 指向的堆上空间。

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

你通过实现 Drop trait 来指定当一个值离开作用域时要运行的代码。Drop trait 要求你实现一个名为 drop 的方法,该方法接受一个对 self 的可变引用。为了看看 Rust 何时调用 drop,让我们暂时用 println! 语句来实现 drop。

Listing 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");
}
Listing 15-14: 一个实现了 Drop trait 的 CustomSmartPointer 结构体,我们会把清理代码放在这里

Drop trait 包含在 prelude 中,所以我们不需要将其引入作用域。我们在 CustomSmartPointer 上实现 Drop trait,并为 drop 方法提供一个调用 println! 的实现。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,执行了我们指定的代码。变量的丢弃顺序与创建顺序相反,所以 d 在 c 之前被丢弃。这个例子的目的是给你一个关于 drop 方法如何工作的直观展示;通常你会指定你的类型需要运行的清理代码,而不是打印消息。

遗憾的是,禁用自动 drop 功能并不直接。禁用 drop 通常没有必要;Drop trait 的全部意义就在于它被自动处理。然而,偶尔你可能想提前清理一个值。一个例子是使用管理锁的智能指针时:你可能想强制调用释放锁的 drop 方法,以便同一作用域中的其他代码可以获取该锁。Rust 不允许你手动调用 Drop trait 的 drop 方法;相反,如果你想强制一个值在其作用域结束前被丢弃,你必须调用标准库提供的 std::mem::drop 函数。

尝试通过修改 Listing 15-14 中的 main 函数来手动调用 Drop trait 的 drop 方法是行不通的,如 Listing 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");
}
Listing 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 -     c.drop();
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。这会导致双重释放错误,因为 Rust 会试图两次清理同一个值。

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

std::mem::drop 函数与 Drop trait 中的 drop 方法不同。我们通过将想要强制丢弃的值作为参数传递来调用它。该函数在 prelude 中,所以我们可以修改 Listing 15-15 中的 main 来调用 drop 函数,如 Listing 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");
}
Listing 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 trait 实现中指定的代码来使清理变得方便和安全:例如,你可以用它来创建自己的内存分配器!有了 Drop trait 和 Rust 的所有权系统,你不必记得去清理,因为 Rust 会自动完成。

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

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