mirror of https://github.com/rust-lang/nomicon
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
5.3 KiB
191 lines
5.3 KiB
8 years ago
|
# Destructors
|
||
10 years ago
|
|
||
10 years ago
|
What the language *does* provide is full-blown automatic destructors through the
|
||
|
`Drop` trait, which provides the following method:
|
||
10 years ago
|
|
||
10 years ago
|
```rust,ignore
|
||
10 years ago
|
fn drop(&mut self);
|
||
|
```
|
||
|
|
||
9 years ago
|
This method gives the type time to somehow finish what it was doing.
|
||
|
|
||
|
**After `drop` is run, Rust will recursively try to drop all of the fields
|
||
|
of `self`.**
|
||
|
|
||
10 years ago
|
This is a convenience feature so that you don't have to write "destructor
|
||
|
boilerplate" to drop children. If a struct has no special logic for being
|
||
|
dropped other than dropping its children, then it means `Drop` doesn't need to
|
||
|
be implemented at all!
|
||
10 years ago
|
|
||
9 years ago
|
**There is no stable way to prevent this behavior in Rust 1.0.**
|
||
10 years ago
|
|
||
9 years ago
|
Note that taking `&mut self` means that even if you could suppress recursive
|
||
10 years ago
|
Drop, Rust will prevent you from e.g. moving fields out of self. For most types,
|
||
|
this is totally fine.
|
||
10 years ago
|
|
||
|
For instance, a custom implementation of `Box` might write `Drop` like this:
|
||
|
|
||
|
```rust
|
||
8 years ago
|
#![feature(alloc, heap_api, unique)]
|
||
9 years ago
|
|
||
|
extern crate alloc;
|
||
10 years ago
|
|
||
9 years ago
|
use std::ptr::{drop_in_place, Unique};
|
||
10 years ago
|
use std::mem;
|
||
|
|
||
9 years ago
|
use alloc::heap;
|
||
|
|
||
10 years ago
|
struct Box<T>{ ptr: Unique<T> }
|
||
10 years ago
|
|
||
|
impl<T> Drop for Box<T> {
|
||
10 years ago
|
fn drop(&mut self) {
|
||
|
unsafe {
|
||
10 years ago
|
drop_in_place(*self.ptr);
|
||
|
heap::deallocate((*self.ptr) as *mut u8,
|
||
|
mem::size_of::<T>(),
|
||
|
mem::align_of::<T>());
|
||
10 years ago
|
}
|
||
|
}
|
||
10 years ago
|
}
|
||
9 years ago
|
# fn main() {}
|
||
10 years ago
|
```
|
||
|
|
||
10 years ago
|
and this works fine because when Rust goes to drop the `ptr` field it just sees
|
||
9 years ago
|
a [Unique] that has no actual `Drop` implementation. Similarly nothing can
|
||
9 years ago
|
use-after-free the `ptr` because when drop exits, it becomes inaccessible.
|
||
10 years ago
|
|
||
|
However this wouldn't work:
|
||
|
|
||
|
```rust
|
||
8 years ago
|
#![feature(alloc, heap_api, unique)]
|
||
9 years ago
|
|
||
|
extern crate alloc;
|
||
10 years ago
|
|
||
9 years ago
|
use std::ptr::{drop_in_place, Unique};
|
||
10 years ago
|
use std::mem;
|
||
|
|
||
9 years ago
|
use alloc::heap;
|
||
|
|
||
10 years ago
|
struct Box<T>{ ptr: Unique<T> }
|
||
10 years ago
|
|
||
|
impl<T> Drop for Box<T> {
|
||
10 years ago
|
fn drop(&mut self) {
|
||
|
unsafe {
|
||
10 years ago
|
drop_in_place(*self.ptr);
|
||
|
heap::deallocate((*self.ptr) as *mut u8,
|
||
|
mem::size_of::<T>(),
|
||
|
mem::align_of::<T>());
|
||
10 years ago
|
}
|
||
|
}
|
||
10 years ago
|
}
|
||
|
|
||
10 years ago
|
struct SuperBox<T> { my_box: Box<T> }
|
||
10 years ago
|
|
||
|
impl<T> Drop for SuperBox<T> {
|
||
10 years ago
|
fn drop(&mut self) {
|
||
|
unsafe {
|
||
|
// Hyper-optimized: deallocate the box's contents for it
|
||
|
// without `drop`ing the contents
|
||
10 years ago
|
heap::deallocate((*self.my_box.ptr) as *mut u8,
|
||
|
mem::size_of::<T>(),
|
||
|
mem::align_of::<T>());
|
||
10 years ago
|
}
|
||
|
}
|
||
10 years ago
|
}
|
||
9 years ago
|
# fn main() {}
|
||
10 years ago
|
```
|
||
|
|
||
|
After we deallocate the `box`'s ptr in SuperBox's destructor, Rust will
|
||
|
happily proceed to tell the box to Drop itself and everything will blow up with
|
||
|
use-after-frees and double-frees.
|
||
|
|
||
9 years ago
|
Note that the recursive drop behavior applies to all structs and enums
|
||
10 years ago
|
regardless of whether they implement Drop. Therefore something like
|
||
|
|
||
|
```rust
|
||
|
struct Boxy<T> {
|
||
10 years ago
|
data1: Box<T>,
|
||
|
data2: Box<T>,
|
||
|
info: u32,
|
||
10 years ago
|
}
|
||
|
```
|
||
|
|
||
|
will have its data1 and data2's fields destructors whenever it "would" be
|
||
|
dropped, even though it itself doesn't implement Drop. We say that such a type
|
||
|
*needs Drop*, even though it is not itself Drop.
|
||
|
|
||
|
Similarly,
|
||
|
|
||
|
```rust
|
||
|
enum Link {
|
||
10 years ago
|
Next(Box<Link>),
|
||
|
None,
|
||
10 years ago
|
}
|
||
|
```
|
||
|
|
||
9 years ago
|
will have its inner Box field dropped if and only if an instance stores the
|
||
10 years ago
|
Next variant.
|
||
10 years ago
|
|
||
9 years ago
|
In general this works really nicely because you don't need to worry about
|
||
10 years ago
|
adding/removing drops when you refactor your data layout. Still there's
|
||
|
certainly many valid usecases for needing to do trickier things with
|
||
|
destructors.
|
||
10 years ago
|
|
||
|
The classic safe solution to overriding recursive drop and allowing moving out
|
||
|
of Self during `drop` is to use an Option:
|
||
|
|
||
|
```rust
|
||
8 years ago
|
#![feature(alloc, heap_api, unique)]
|
||
9 years ago
|
|
||
|
extern crate alloc;
|
||
10 years ago
|
|
||
9 years ago
|
use std::ptr::{drop_in_place, Unique};
|
||
10 years ago
|
use std::mem;
|
||
|
|
||
9 years ago
|
use alloc::heap;
|
||
|
|
||
10 years ago
|
struct Box<T>{ ptr: Unique<T> }
|
||
10 years ago
|
|
||
|
impl<T> Drop for Box<T> {
|
||
10 years ago
|
fn drop(&mut self) {
|
||
|
unsafe {
|
||
10 years ago
|
drop_in_place(*self.ptr);
|
||
|
heap::deallocate((*self.ptr) as *mut u8,
|
||
|
mem::size_of::<T>(),
|
||
|
mem::align_of::<T>());
|
||
10 years ago
|
}
|
||
|
}
|
||
10 years ago
|
}
|
||
|
|
||
10 years ago
|
struct SuperBox<T> { my_box: Option<Box<T>> }
|
||
10 years ago
|
|
||
|
impl<T> Drop for SuperBox<T> {
|
||
10 years ago
|
fn drop(&mut self) {
|
||
|
unsafe {
|
||
|
// Hyper-optimized: deallocate the box's contents for it
|
||
|
// without `drop`ing the contents. Need to set the `box`
|
||
|
// field as `None` to prevent Rust from trying to Drop it.
|
||
10 years ago
|
let my_box = self.my_box.take().unwrap();
|
||
|
heap::deallocate((*my_box.ptr) as *mut u8,
|
||
|
mem::size_of::<T>(),
|
||
|
mem::align_of::<T>());
|
||
|
mem::forget(my_box);
|
||
10 years ago
|
}
|
||
|
}
|
||
10 years ago
|
}
|
||
9 years ago
|
# fn main() {}
|
||
10 years ago
|
```
|
||
|
|
||
10 years ago
|
However this has fairly odd semantics: you're saying that a field that *should*
|
||
9 years ago
|
always be Some *may* be None, just because that happens in the destructor. Of
|
||
10 years ago
|
course this conversely makes a lot of sense: you can call arbitrary methods on
|
||
|
self during the destructor, and this should prevent you from ever doing so after
|
||
|
deinitializing the field. Not that it will prevent you from producing any other
|
||
10 years ago
|
arbitrarily invalid state in there.
|
||
|
|
||
|
On balance this is an ok choice. Certainly what you should reach for by default.
|
||
|
However, in the future we expect there to be a first-class way to announce that
|
||
10 years ago
|
a field shouldn't be automatically dropped.
|
||
9 years ago
|
|
||
|
[Unique]: phantom-data.html
|