drop, the destructor of Rust structs

drop impl struct mut

In Object Oriented Programming languages you can usually define a special method that will be called when the instance goes out of scope or otherwise destroyed. The generic name of this method is "destructor" though in different programming languages the specific name might be different.

Rust does not have classes and object in the same way as OOP languages have, but it has structs and we can implemented a method called drop that will be the destructor of that struct. Meaning it will be called when the struct is destroyed.

It is described in the Running Code on Cleanup with the Drop Trait chapter of the Rust book, but as always I was interested to see it myself. I was also wondering, how does this behave in case of a panic!?

This is a very simple and rather stupid example in which the user needs to supply two numbers and we divide one by the other. This is a nice way to allow the user to create a panic! without explicitly calling panic! which might have other impacts.

We also have two instances of the Thing struct that does not do much, but has a drop method implemented.

examples/drop/src/main.rs

#![allow(unused)]

struct Thing {
    name: String,
}

impl Drop for Thing {
    fn drop(&mut self) {
        println!("drop for {}", self.name);
    }
}

fn main() {
    let args = std::env::args().collect::<Vec<String>>();
    if args.len() != 3 {
        eprintln!("Usage: {} dividend divisor", args[0]);
        std::process::exit(1);
    }

    let a = Thing { name: String::from("first") };
    let b = Thing { name: String::from("second") };

    let dividend = args[1].parse::<f64>().unwrap();
    let divisor = args[2].parse::<f64>().unwrap();

    println!("{} / {}  =  {}", dividend, divisor, dividend/divisor);

    if dividend == 42.0 {
        let c = Thing { name: String::from("apple") };
        println!("after the apple was created");
        let c = Thing { name: String::from("banana") };
        println!("after the banana was created");
    }

    if divisor == 42.0 {
        let mut d = Thing { name: String::from("cat") };
        println!("after the cat was created");
        d = Thing { name: String::from("dog") };
        println!("after the dog was created");
    }

}

When there is no panic!

The first time I run the code I verify that 10 divided by 2 is 5. That works nicely. You can see that basically after the main function ends so when the variables a and b go out of scope the program calls the drop method of the struct that prints the two messages.

You can also notice that the messages are printed in the reverse order how the variables were created.

$ cargo run -q 10 2
10 / 2  =  5
drop for second
drop for first

When there is panic!

So what happens if we try to divide by 0?

$ cargo run -q 10 0
10 / 0  =  inf
drop for second
drop for first

Oups, there is no panic! Apparently Rust can divide by 0. Well, at least floating point numbers. If I replace the f64 by some integer, eg. u64 then we would get a panic! here.

Ok, but no all is lost we can try something else:

$ cargo run -q 10 zero
thread 'main' panicked at src/main.rs:24:42:
called `Result::unwrap()` on an `Err` value: ParseFloatError { kind: Invalid }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
drop for second
drop for first

That's much better. Now the unwrap panicked.

As you can see the drop method was still called.

That's the same if I run with --release

$ cargo run -q --release 10 zero
thread 'main' panicked at src/main.rs:24:42:
called `Result::unwrap()` on an `Err` value: ParseFloatError { kind: Invalid }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
drop for second
drop for first

Whet if we replace the content of the variable? (shadowing)

At the end of the code we have two special cases.

In the fist case, if the dividend is 42 then we create a variable and then we replace it with another instance of the same struct.

Because this what Rust refers to as shadowing the first occurrence of c is not destroyed when the new Thing is assigned to it. (Despite the fact that in this example they will go out of scope together.) That means the Thing which is called apple is dropped only at the end of the if-block after the one with the banana is dropped.

$ cargo run -q --release 42 2
42 / 2  =  21
after the apple was created
after the banana was created
drop for banana
drop for apple
drop for second
drop for first

What if we replace the mutable variable?

In the 2nd special case we mad the variable d mutable. In this case the result is slightly different.

The struct called cat is dropped immediately when the struct called dog is created.

$ cargo run -q --release 2 42
2 / 42  =  0.047619047619047616
after the cat was created
drop for cat
after the dog was created
drop for dog
drop for second
drop for first

Related Pages

Write to a file - create file in Rust
Keep you data safe using advisory lock on your files
Elapsed time logger

Author

Gabor Szabo (szabgab)

Gabor Szabo, the author of the Rust Maven web site maintains several Open source projects in Rust and while he still feels he has tons of new things to learn about Rust he already offers training courses in Rust and still teaches Python, Perl, git, GitHub, GitLab, CI, and testing.

Gabor Szabo