Rust Drop Trait

Implementing Drop in Rust

Drop is a trait that allows specified code to be triggered when a value goes out of scope. The Drop trait is provided by the Rust standard library.

Drop is simple to understand: when a value goes out of scope, a specified block of code executes. The Drop trait can be manually implemented for any type, but is automatically implemented for most types. Manual implementation allows us to customize code to be run when a type is dropped.

This tutorial covers the Drop trait, including a brief overview of destructors, how to implement the Drop trait, and important rules regarding the order in which Drop gets called.

Destructors

Whenever a value is no longer needed or goes out of scope, Rust calls a destructor to free its’ resources. There are different circumstances in which a destructor runs, but the most common case is when a value reaches the end of its’ scope.

The significance of Drop is that it allows for the inclusion of custom code within the destructor. This code will run when the destructor gets called to deallocate the resources for that specific value.

The Drop Trait

We can learn a lot from the code for the drop trait:

pub trait Drop {
    fn drop(&mut self);
}

Note that Drop has one method, called drop().

If this trait is implemented for a type, the destructor will make a call to Drop::drop.

Implementing the Drop Trait

The following example demonstrates how this works. First, we implement Drop for the struct CanBeDropped and then create a variable binding named _x, which is of type CanBeDropped.

When _x goes out of scope, Drop::drop is called and the resulting code block inside the drop method executes:

struct CanBeDropped;

impl Drop for CanBeDropped {
    fn drop(&mut self) {
        println!("This line executes when _x goes out of scope");
    }
}

fn main() {
    let _x = CanBeDropped;
    println!("This line executes first");

} // x goes out of scope here, triggering Drop

Standard Output:

This line executes first
This line executes when _x goes out of scope

Ordering Rules of Drop

Drop is really just part of the destructor, and obeys the rules that govern destructors. When the end of a code block is reached, it is common for multiple objects or items to reach the end of their scope at the same time. However, a destructor can’t run on each one simultaneously so there is an order to which the destructors (and the resulting call to Drop::drop) are run.

One of the most important examples of this is that values are dropped in the opposite order in which they are declared (LIFO). In the following example, Bob is dropped before Alice because Alice is declared first:

struct Person {
    name: String,
    age: u8,
}

impl Drop for Person {
    fn drop(&mut self) {
        println!("{} has been dropped", self.name);
    }
}

fn main() {
    let person1 = Person {
        name: String::from("Alice"),
        age: 25,
    };
    let person2 = Person {
        name: String::from("Bob"),
        age: 30,
    };
}

Standard Output:

Bob has been dropped
Alice has been dropped

Here are some of the most important ordering rules of Drop:

  • Variables are dropped in the opposite order in which they are declared.
  • Struct fields are dropped in declaration order.
  • Enum fields are dropped in declaration order.
  • Tuple fields are dropped in order.
  • Elements of an array are dropped in sequential order from the first element to the last.