Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Running Code on Cleanup with the Drop Trait

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

The second trait important to the smart pointer pattern is Drop, which lets you customize what happens when a value is about to go out of scope. You can provide an implementation for the Drop trait on any type, and that code can be used to release resources like files or network connections.

我们是在智能指针的上下文中引入 Drop 的,因为 Drop trait 的功能几乎总是在实现智能指针时使用。例如,当一个 Box<T> 被丢弃(dropped)时,它会释放该 box 指向的堆空间。

We’re introducing Drop in the context of smart pointers because the functionality of the Drop trait is almost always used when implementing a smart pointer. For example, when a Box<T> is dropped, it will deallocate the space on the heap that the box points to.

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

In some languages, for some types, the programmer must call code to free memory or resources every time they finish using an instance of those types. Examples include file handles, sockets, and locks. If the programmer forgets, the system might become overloaded and crash. In Rust, you can specify that a particular bit of code be run whenever a value goes out of scope, and the compiler will insert this code automatically. As a result, you don’t need to be careful about placing cleanup code everywhere in a program that an instance of a particular type is finished with—you still won’t leak resources!

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

You specify the code to run when a value goes out of scope by implementing the Drop trait. The Drop trait requires you to implement one method named drop that takes a mutable reference to self. To see when Rust calls drop, let’s implement drop with println! statements for now.

示例 15-14 展示了一个 CustomSmartPointer 结构体,其唯一自定义的功能是当实例超出作用域时打印 Dropping CustomSmartPointer!,以显示 Rust 何时运行 drop 方法。

Listing 15-14 shows a CustomSmartPointer struct whose only custom functionality is that it will print Dropping CustomSmartPointer! when the instance goes out of scope, to show when Rust runs the drop method.

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,并为调用 println!drop 方法提供了一个实现。drop 方法体是你放置任何想要在类型实例超出作用域时运行的逻辑的地方。我们在这里打印一些文本,以便直观地演示 Rust 何时调用 drop

The Drop trait is included in the prelude, so we don’t need to bring it into scope. We implement the Drop trait on CustomSmartPointer and provide an implementation for the drop method that calls println!. The body of the drop method is where you would place any logic that you wanted to run when an instance of your type goes out of scope. We’re printing some text here to demonstrate visually when Rust will call drop.

main 函数中,我们创建了两个 CustomSmartPointer 实例,然后打印 CustomSmartPointers created。在 main 结束时,我们的 CustomSmartPointer 实例将超出作用域,Rust 将调用我们在 drop 方法中放置的代码,打印出最后的消息。请注意,我们不需要显式调用 drop 方法。

In main, we create two instances of CustomSmartPointer and then print CustomSmartPointers created. At the end of main, our instances of CustomSmartPointer will go out of scope, and Rust will call the code we put in the drop method, printing our final message. Note that we didn’t need to call the drop method explicitly.

运行该程序时,我们将看到以下输出:

When we run this program, we’ll see the following output:

$ 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 方法的工作原理;通常你会指定你的类型需要运行的清理代码,而不是打印消息。

Rust automatically called drop for us when our instances went out of scope, calling the code we specified. Variables are dropped in the reverse order of their creation, so d was dropped before c. This example’s purpose is to give you a visual guide to how the drop method works; usually you would specify the cleanup code that your type needs to run rather than a print message.

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

Unfortunately, it’s not straightforward to disable the automatic drop functionality. Disabling drop isn’t usually necessary; the whole point of the Drop trait is that it’s taken care of automatically. Occasionally, however, you might want to clean up a value early. One example is when using smart pointers that manage locks: You might want to force the drop method that releases the lock so that other code in the same scope can acquire the lock. Rust doesn’t let you call the Drop trait’s drop method manually; instead, you have to call the std::mem::drop function provided by the standard library if you want to force a value to be dropped before the end of its scope.

尝试通过修改示例 15-14 中的 main 函数来手动调用 Drop trait 的 drop 方法是行不通的,如示例 15-15 所示。

Trying to call the Drop trait’s drop method manually by modifying the main function from Listing 15-14 won’t work, as shown in Listing 15-15.

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");
}

当我们尝试编译这段代码时,会得到如下错误:

When we try to compile this code, we’ll get this error:

$ 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 函数是一种特殊的析构函数。

This error message states that we’re not allowed to explicitly call drop. The error message uses the term destructor, which is the general programming term for a function that cleans up an instance. A destructor is analogous to a constructor, which creates an instance. The drop function in Rust is one particular destructor.

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

Rust doesn’t let us call drop explicitly, because Rust would still automatically call drop on the value at the end of main. This would cause a double free error because Rust would be trying to clean up the same value twice.

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

We can’t disable the automatic insertion of drop when a value goes out of scope, and we can’t call the drop method explicitly. So, if we need to force a value to be cleaned up early, we use the std::mem::drop function.

std::mem::drop 函数不同于 Drop trait 中的 drop 方法。我们通过将想要强制丢弃的值作为参数传递来调用它。该函数包含在 prelude 中,因此我们可以修改示例 15-15 中的 main 来调用 drop 函数,如示例 15-16 所示。

std::mem::drop function is different from the drop method in the Drop trait. We call it by passing as an argument the value we want to force-drop. The function is in the prelude, so we can modify main in Listing 15-15 to call the drop function, as shown in Listing 15-16.

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");
}

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

Running this code will print the following:

$ 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 createdCustomSmartPointer dropped before the end of main 之间,显示 drop 方法代码在那个点被调用以丢弃 c

The text Dropping CustomSmartPointer with data `some data`! is printed between the CustomSmartPointer created and CustomSmartPointer dropped before the end of main text, showing that the drop method code is called to drop c at that point.

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

You can use code specified in a Drop trait implementation in many ways to make cleanup convenient and safe: For instance, you could use it to create your own memory allocator! With the Drop trait and Rust’s ownership system, you don’t have to remember to clean up, because Rust does it automatically.

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

You also don’t have to worry about problems resulting from accidentally cleaning up values still in use: The ownership system that makes sure references are always valid also ensures that drop gets called only once when the value is no longer being used.

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

Now that we’ve examined Box<T> and some of the characteristics of smart pointers, let’s look at a few other smart pointers defined in the standard library.