From c2014ea50da7d69cd85616ca545175478cb15a00 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 23 Aug 2017 14:07:43 -0400 Subject: [PATCH] Edits to 15-03 --- second-edition/dictionary.txt | 1 + second-edition/src/ch15-03-drop.md | 258 +++++++++++++++++++---------- 2 files changed, 176 insertions(+), 83 deletions(-) diff --git a/second-edition/dictionary.txt b/second-edition/dictionary.txt index d077ce7e4..fe21fa564 100644 --- a/second-edition/dictionary.txt +++ b/second-edition/dictionary.txt @@ -70,6 +70,7 @@ ctrl Ctrl customizable CustomSmartPointer +deallocate deallocated deallocating deallocation diff --git a/second-edition/src/ch15-03-drop.md b/second-edition/src/ch15-03-drop.md index d5c1d5d30..fc3488a94 100644 --- a/second-edition/src/ch15-03-drop.md +++ b/second-edition/src/ch15-03-drop.md @@ -1,32 +1,53 @@ ## The `Drop` Trait Runs Code on Cleanup -The other trait that’s important to the smart pointer pattern is the `Drop` -trait. `Drop` lets us run some code when a value is about to go out of scope. -Smart pointers perform important cleanup when being dropped, like deallocating -memory or decrementing a reference count. More generally, data types can manage -resources beyond memory, like files or network connections, and use `Drop` to -release those resources when our code is done with them. We’re discussing -`Drop` in the context of smart pointers, though, because the functionality of -the `Drop` trait is almost always used when implementing smart pointers. +The second trait important to the smart pointer pattern is `Drop`, which lets +us customize what happens when a value is about to go out of scope. We can +provide an implementation for the `Drop` trait on any type, and the code we +specify can be used to release resources like files or network connections. +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, `Box` customizes `Drop` in order to deallocate +the space on the heap that the box points to. -In some other languages, we have to remember to call code to free the memory or -resource every time we finish using an instance of a smart pointer. If we -forget, the system our code is running on might get overloaded and crash. In -Rust, we can specify that some code should be run when a value goes out of -scope, and the compiler will insert this code automatically. That means we don’t -need to remember to put this code everywhere we’re done with an instance of -these types, but we still won’t leak resources! +In some languages, the programmer must call code to free memory or resources +every time they finish using an instance of a smart pointer. If they forget, +the system might become overloaded and crash. In Rust, we can specify that a +particular bit of code should be run whenever a value goes out of scope, and +the compiler will insert this code automatically. -The way we specify code should be run when a value goes out of scope is by -implementing the `Drop` trait. The `Drop` trait requires us to implement one -method named `drop` that takes a mutable reference to `self`. + + -Listing 15-8 shows a `CustomSmartPointer` struct that doesn’t actually do -anything, but we’re printing out `CustomSmartPointer created.` right after we -create an instance of the struct and `Dropping CustomSmartPointer!` when the -instance goes out of scope so that we can see when each piece of code gets run. -Instead of a `println!` statement, you’d fill in `drop` with whatever cleanup -code your smart pointer needs to run: +This means we don't need be careful about placing clean up code everywhere in a +program that an instance of a particular type is finished with, but we still +won't leak resources! + +We specify the code to run when a value goes out of scope by implementing the +`Drop` trait. The `Drop` trait requires us to implement one method named `drop` +that takes a mutable reference to `self`. In order to be able to see when Rust +calls `drop`, let's implement `drop` with `println!` statements for now. + + + + +Listing 15-8 shows a `CustomSmartPointer` struct whose only custom +functionality is that it will print out `Dropping CustomSmartPointer!` when the +instance goes out of scope. This will demonstrate when Rust runs the `drop` +function: + + + Filename: src/main.rs @@ -44,40 +65,66 @@ impl Drop for CustomSmartPointer { fn main() { let c = CustomSmartPointer { data: String::from("some data") }; println!("CustomSmartPointer created."); - println!("Wait for it..."); } ``` Listing 15-8: A `CustomSmartPointer` struct that -implements the `Drop` trait, where we could put code that would clean up after -the `CustomSmartPointer`. +implements the `Drop` trait, where we would put our clean up code. -The `Drop` trait is in the prelude, so we don’t need to import it. The `drop` -method implementation calls the `println!`; this is where you’d put the actual -code needed to close the socket. In `main`, we create a new instance of -`CustomSmartPointer` then print out `CustomSmartPointer created.` to be able to -see that our code got to that point at runtime. At the end of `main`, our -instance of `CustomSmartPointer` will go out of scope. Note that we didn’t call -the `drop` method explicitly. +The `Drop` trait is included in the prelude, so we don't need to import it. We +implement the `Drop` trait on `CustomSmartPointer`, and provide an +implementation for the `drop` method that calls `println!`. The body of the +`drop` function is where you'd put any logic that you wanted to run when an +instance of your type goes out of scope. We're choosing to print out some text +here in order to demonstrate when Rust will call `drop`. -When we run this program, we’ll see: + + + +In `main`, we create a new instance of `CustomSmartPointer` and then print out +`CustomSmartPointer created.`. At the end of `main`, our instance 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: ```text CustomSmartPointer created. -Wait for it... Dropping CustomSmartPointer! ``` -printed to the screen, which shows that Rust automatically called `drop` for us -when our instance went out of scope. +Rust automatically called `drop` for us when our instance went out of scope, +calling the code we specified. This is just to give you a visual guide to how +the drop method works, but usually you would specify the cleanup code that your +type needs to run rather than a print message. -We can use the `std::mem::drop` function to drop a value earlier than when it -goes out of scope. This isn’t usually necessary; the whole point of the `Drop` -trait is that it’s taken care of automatically for us. We’ll see an example of -a case when we’ll need to drop a value earlier than when it goes out of scope -in Chapter 16 when we’re talking about concurrency. For now, let’s just see -that it’s possible, and `std::mem::drop` is in the prelude so we can just call -`drop` as shown in Listing 15-9: + + + +#### Dropping a Value Early with `std::mem::drop` + + + + +Rust inserts the call to `drop` automatically when a value goes out of scope, +and there's no way to disable this functionality if we want to force a value to +clean itself up early. This isn't usually necessary; the whole point of the +`Drop` trait is that it's taken care of automatically for us. Occasionally you +may find that you want to clean up a value early. One example is when using +smart pointers that manage locks; you may want to force the `drop` method that +releases the lock to run so that other code in the same scope can acquire the +lock. First, let's see what happens if we try to call the `Drop` trait's `drop` +method ourselves by modifying the `main` function from Listing 15-8 as shown in +Listing 15-9: + + + Filename: src/main.rs @@ -85,56 +132,101 @@ that it’s possible, and `std::mem::drop` is in the prelude so we can just call fn main() { let c = CustomSmartPointer { data: String::from("some data") }; println!("CustomSmartPointer created."); - drop(c); - println!("Wait for it..."); + c.drop(); + println!("CustomSmartPointer dropped before the end of main."); } ``` -Listing 15-9: Calling `std::mem::drop` to explicitly drop -a value before it goes out of scope +Listing 15-9: Attempting to call the `drop` method from +the `Drop` trait manually to clean up early -Running this code will print the following, showing that the destructor code is -called since `Dropping CustomSmartPointer!` is printed between -`CustomSmartPointer created.` and `Wait for it...`: +If we try to compile this, we'll get this error: + +```text +error[E0040]: explicit use of destructor method + --> src/main.rs:15:7 + | +15 | c.drop(); + | ^^^^ explicit destructor calls not allowed +``` + +This error message says 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* that creates an instance. The `drop` function in Rust is one +particular destructor. + +Rust doesn't let us call `drop` explicitly because Rust would still +automatically call `drop` on the value at the end of `main`, and this would be +a *double free* error since Rust would be trying to clean up the same value +twice. + +Because 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, if we need to +force a value to be cleaned up early, we can use the `std::mem::drop` function. + +The `std::mem::drop` function is different than the `drop` method in the `Drop` +trait. We call it by passing the value we want to force to be dropped early as +an argument. `std::mem::drop` is in the prelude, so we can modify `main` from +Listing 15-8 to call the `drop` function as shown in Listing 15-10: + +Filename: src/main.rs + +```rust +# 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."); + drop(c); + println!("CustomSmartPointer dropped before the end of main."); +} +``` + +Listing 15-10: Calling `std::mem::drop` to explicitly +drop a value before it goes out of scope + +Running this code will print the following: ```text CustomSmartPointer created. Dropping CustomSmartPointer! -Wait for it... +CustomSmartPointer dropped before the end of main. ``` -Note that we aren’t allowed to call the `drop` method that we defined directly: -if we replaced `drop(c)` in Listing 15-9 with `c.drop()`, we’ll get a compiler -error that says `explicit destructor calls not allowed`. We’re not allowed to -call `Drop::drop` directly because when Rust inserts its call to `Drop::drop` -automatically when the value goes out of scope, then the value would get -dropped twice. Dropping a value twice could cause an error or corrupt memory, -so Rust doesn’t let us. Instead, we can use `std::mem::drop`, whose definition -is: + + -```rust -pub mod std { - pub mod mem { - pub fn drop(x: T) { } - } -} -``` +The `Dropping CustomSmartPointer!` is printed between `CustomSmartPointer +created.` and `CustomSmartPointer dropped before the end of main.`, showing +that the `drop` method code is called to drop `c` at that point. -This function is generic over any type `T`, so we can pass any value to it. The -function doesn’t actually have anything in its body, so it doesn’t use its -parameter. The reason this empty function is useful is that `drop` takes -ownership of its parameter, which means the value in `x` gets dropped at the -end of this function when `x` goes out of scope. + + -Code specified in a `Drop` trait implementation can be used for many reasons to +Code specified in a `Drop` trait implementation can be used in many ways to make cleanup convenient and safe: we could use it to create our own memory -allocator, for instance! By using the `Drop` trait and Rust’s ownership system, -we don’t have to remember to clean up after ourselves since Rust takes care of -it automatically. We’ll get compiler errors if we write code that would clean -up a value that’s still in use, since the ownership system that makes sure -references are always valid will also make sure that `drop` only gets called -one time when the value is no longer being used. +allocator, for instance! With the `Drop` trait and Rust's ownership system, you +don't have to remember to clean up after yourself, Rust takes care of it +automatically. -Now that we’ve gone over `Box` and some of the characteristics of smart -pointers, let’s talk about a few other smart pointers defined in the standard -library that add different kinds of useful functionality. +We also don't have to worry about accidentally cleaning up values still in use +because that would cause a compiler error: the ownership system that makes sure +references are always valid will also make sure that `drop` only gets called +once when the value is no longer being used. + +Now that we've gone over `Box` and some of the characteristics of smart +pointers, let's talk about a few other smart pointers defined in the standard +library.