You are reading a draft of the next edition of TRPL. For more, go here.

The Drop Trait Runs Code on Cleanup

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<T> customizes Drop in order to deallocate the space on the heap that the box points to.

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.

This means we don’t need to 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

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-8: A CustomSmartPointer struct that implements the Drop trait, where we would put our clean up code.

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.

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:

CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

Rust automatically called drop for us when our instance went out of scope, calling the code we specified. Variables are dropped in the reverse order of the order in which they were created, so d was dropped before c. 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.

Dropping a Value Early with std::mem::drop

Rust inserts the call to drop automatically when a value goes out of scope, and it’s not straightforward to disable this functionality. Disabling drop 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

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-9: Attempting to call the drop method from the Drop trait manually to clean up early

If we try to compile this, we’ll get this error:

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

# 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:

CustomSmartPointer created.
Dropping CustomSmartPointer!
CustomSmartPointer dropped before the end of main.

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.

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! 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.

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<T> and some of the characteristics of smart pointers, let’s talk about a few other smart pointers defined in the standard library.