7.9 KiB
Running Code on Cleanup with the Drop
Trait
The second trait improtant to the smart pointer pattern is Drop
This lets you customize what happens when a values 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
traits are almost always used when implementing a smart pointer but it is also useful in other cases/types.
For example when a Box<T>
is dropped it will deallocate the space on the heap that the box points to.
In some languages the programmer must call code to free emory or resources when they finished using an instance of those types.
Some examples include file handles, sockets, or locks.
If they forget, the system might become overloaded and crash.
In Rust you can specify that a particluar bit of code that will run whenever a value gos out of scope.
The compler will insert this code automatically.
This results that you don't need to be careful about placing cleanup code everywhere in a program that an instance of a paritcular type is finished with.
You still won't leak resources.
The code that you want to run when a value goes out of scope goes in the implementation of the Drop
trait.
The Drop
trait requires you to implement one method named drop
that takes a mutable refernce to self
to see when Rust calls drop
we will implement drop
with println!
statements.
In this example it shows a CustomSmartPoionter
struct whose only custom functionality is that it will print Dropping CustomSmartPointer!
when the instance goes out of scope.
This is in order to show when Rust runs the drop
function.
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.");
}
The Drop Trait is included in the prelude so we don't need to bring it into scope.
Here we implemented the Drop
trait on CustomSmartPointer
and provide an implementation for the drop
method that calls println!
.
Here the body of the drop
function is where you would place any logic that you wanted to run when an instance of your type goes out of cope.
We print some text here to demonstrate visually when Rust will call drop
.
In main
we create two instances of CustomSmartPointer
and then print CustomSmartPointers created
At the end of main
when the insatnces of CustomSmartPointer
go out of scope.
Rust will call the code we put in the drop
method.
This printed our final message.
Notice that we didn't need to call the drop
method explicity.
Here is the output from that code
$ 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 will automatically call drop
for us when our instances go out of scope, calling the code speicifed by drop
.
Varialbes are dropped in the reverse order they we created.
Here it means that d
was dropped before c
This examples gives a visual guide on how the drop
method works.
Usually you would specify the cleanup oe that your type needs to run rather than a print message.
Dropping a Valu Early with std::mem::drop
This is not straightforward to to disable the automatic drop
functionality.
This is usually unnecessary to disable drop
.
The whole point of the Drop
trait is that it is taken care of automatically.
Very rarely you might want to clean up a value early.
One example us when using smart pointers that manage your locks.
Here you may want to force the drop
method that releases the lock so that other code in the same scope can aquire the lock.
Rust doesnt let you call the Drop
trait drop
method manually.
Here you would instead call the std::mem::drop
fnction provided by te std library if you want to force a value to be dropped before the end of a scope.
If we try to call the drop
method manually.
In this case we would get a compiler error
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.");
}
Here is the error output
$ 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 | 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
This error message states that we are not allowed to explicitly call drop
.
The error mesage uses the term destructor, this is a general programming term for a function that cleans up an instance.
A destructor is analogous to a constructor, this creates an instance.
The drop
function in Rust is one particular destructor.
Rust doesn't let us call drop
explicity 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.
We can't disable this automatic insertion of drop
when a value goes out of scope.
We also can't call the drop
method explicitly.
If we need to force a value to be cleaned up early, then we use the std::mem::drop
function.
The std::mem::drop
function is different from the drop
method in the Drop
trait.
We call this by passing the value we want to force to drop as an argument.
Here is the updated version with the function being in the prelude.
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.");
}
Here we get this output
$ 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.
The tet Dropping CustomSmartPointer with data 'some data'!
is printed between the CustomSmartPointer created.
and CustomSmartPointer dropped before the end of main.
text.
This indicates that the drop
method code is called to drop c
at that point.
You can then use the 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 dont have to remember to clean up because Rust will do it automatically.
You also dont have to worry about problems resulting from accidentally cleaning up valuess still in use.
The ownership system will ensure that references are always valid, also enusreing that drop
gets called only once when the value is no longer being used.