RustBrock/Drop Trait.md
darkicewolf50 0052658af0
Some checks failed
Test Gitea Actions / first (push) Successful in 41s
Test Gitea Actions / check-code (push) Failing after 16s
Test Gitea Actions / test (push) Has been skipped
Test Gitea Actions / documentation-check (push) Has been skipped
finihed ch15.3
2025-03-04 16:55:02 -07:00

218 lines
7.9 KiB
Markdown

# 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.
```rust
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
```rust
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.
```rust
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.