Skip to content

Update drop-related stuff based on improvements to the language #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* [Higher-Rank Trait Bounds](hrtb.md)
* [Subtyping and Variance](subtyping.md)
* [Drop Check](dropck.md)
* [Drop Check Escape Patch](dropck-eyepatch.md)
* [PhantomData](phantom-data.md)
* [Splitting Borrows](borrow-splitting.md)
* [Type Conversions](conversions.md)
Expand Down
72 changes: 38 additions & 34 deletions src/destructors.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,54 @@ 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!

**There is no stable way to prevent this behavior in Rust 1.0.**
If this behaviour is unacceptable, it can be supressed by placing each field
you don't want to drop in a `union`. The standard library provides the
[`mem::ManuallyDrop`][ManuallyDrop] wrapper type as a convience for doing this.

Note that taking `&mut self` means that even if you could suppress recursive
Drop, Rust will prevent you from e.g. moving fields out of self. For most types,
this is totally fine.

For instance, a custom implementation of `Box` might write `Drop` like this:

Consider a custom implementation of `Box`, which might write `Drop` like this:

```rust
#![feature(unique, allocator_api)]
#![feature(allocator_api)]

use std::heap::{Heap, Alloc, Layout};
use std::mem;
use std::ptr::{drop_in_place, Unique};
use std::ptr::drop_in_place;

struct Box<T>{ ptr: Unique<T> }
struct Box<T>{ ptr: *mut T }

impl<T> Drop for Box<T> {
fn drop(&mut self) {
unsafe {
drop_in_place(self.ptr.as_ptr());
Heap.dealloc(self.ptr.as_ptr() as *mut u8, Layout::new::<T>())
drop_in_place(self.ptr);
Heap.dealloc(self.ptr as *mut u8, Layout::new::<T>())
}
}
}
# fn main() {}
```

and this works fine because when Rust goes to drop the `ptr` field it just sees
a [Unique] that has no actual `Drop` implementation. Similarly nothing can
This works fine because when Rust goes to drop the `ptr` field it just sees
a `*mut T` that has no actual `Drop` implementation. Similarly nothing can
use-after-free the `ptr` because when drop exits, it becomes inaccessible.

However this wouldn't work:

```rust
#![feature(allocator_api, unique)]
#![feature(allocator_api)]

use std::heap::{Heap, Alloc, Layout};
use std::ptr::{drop_in_place, Unique};
use std::ptr::drop_in_place;
use std::mem;

struct Box<T>{ ptr: Unique<T> }
struct Box<T>{ ptr: *mut T }

impl<T> Drop for Box<T> {
fn drop(&mut self) {
unsafe {
drop_in_place(self.ptr.as_ptr());
Heap.dealloc(self.ptr.as_ptr() as *mut u8, Layout::new::<T>());
drop_in_place(self.ptr);
Heap.dealloc(self.ptr as *mut u8, Layout::new::<T>());
}
}
}
Expand All @@ -74,17 +74,17 @@ struct SuperBox<T> { my_box: Box<T> }
impl<T> Drop for SuperBox<T> {
fn drop(&mut self) {
unsafe {
// Hyper-optimized: deallocate the box's contents for it
// """Hyper-optimized""": deallocate the box's contents for it
// without `drop`ing the contents
Heap.dealloc(self.my_box.ptr.as_ptr() as *mut u8, Layout::new::<T>());
Heap.dealloc(self.my_box.ptr as *mut u8, Layout::new::<T>());
}
}
}
# fn main() {}
```

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
After we deallocate `my_box`'s ptr in SuperBox's destructor, Rust will
happily proceed to tell `my_box` to Drop itself and everything will blow up with
use-after-frees and double-frees.

Note that the recursive drop behavior applies to all structs and enums
Expand All @@ -98,9 +98,10 @@ struct Boxy<T> {
}
```

will have its data1 and data2's fields destructors whenever it "would" be
will have its `data1` and `data2` fields' destructors run 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.
*needs Drop*, even though it is not itself Drop. This property can be checked
for with the [`mem::needs_drop()`][needs_drop] function.

Similarly,

Expand All @@ -115,27 +116,27 @@ will have its inner Box field dropped if and only if an instance stores the
Next variant.

In general this works really nicely because you don't need to worry about
adding/removing drops when you refactor your data layout. Still there's
adding/removing drops when you refactor your data layout. But there's
certainly many valid usecases for needing to do trickier things with
destructors.

The classic safe solution to overriding recursive drop and allowing moving out
The classic safe solution to preventing recursive drop and allowing moving out
of Self during `drop` is to use an Option:

```rust
#![feature(allocator_api, unique)]
#![feature(allocator_api)]

use std::heap::{Alloc, Heap, Layout};
use std::ptr::{drop_in_place, Unique};
use std::ptr::drop_in_place;
use std::mem;

struct Box<T>{ ptr: Unique<T> }
struct Box<T>{ ptr: *mut T }

impl<T> Drop for Box<T> {
fn drop(&mut self) {
unsafe {
drop_in_place(self.ptr.as_ptr());
Heap.dealloc(self.ptr.as_ptr() as *mut u8, Layout::new::<T>());
drop_in_place(self.ptr);
Heap.dealloc(self.ptr as *mut u8, Layout::new::<T>());
}
}
}
Expand All @@ -149,7 +150,7 @@ impl<T> Drop for SuperBox<T> {
// without `drop`ing the contents. Need to set the `box`
// field as `None` to prevent Rust from trying to Drop it.
let my_box = self.my_box.take().unwrap();
Heap.dealloc(my_box.ptr.as_ptr() as *mut u8, Layout::new::<T>());
Heap.dealloc(my_box.ptr as *mut u8, Layout::new::<T>());
mem::forget(my_box);
}
}
Expand All @@ -165,7 +166,10 @@ deinitializing the field. Not that it will prevent you from producing any other
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
a field shouldn't be automatically dropped.

[Unique]: phantom-data.html
Should using Option be unacceptable, [`ManuallyDrop`][ManuallyDrop] is always
Copy link
Contributor

@arielb1 arielb1 Aug 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eng: this is awkward. "However, if using an Option is unacceptable..." would be better

available.


[ManuallyDrop]: https://doc.rust-lang.org/std/mem/union.ManuallyDrop.html
[needs_drop]: https://doc.rust-lang.org/nightly/std/mem/fn.needs_drop.html
10 changes: 6 additions & 4 deletions src/drop-flags.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Drop Flags

The examples in the previous section introduce an interesting problem for Rust.
We have seen that it's possible to conditionally initialize, deinitialize, and
We've seen that it's possible to conditionally initialize, deinitialize, and
reinitialize locations of memory totally safely. For Copy types, this isn't
particularly notable since they're just a random pile of bits. However types
with destructors are a different story: Rust needs to know whether to call a
Expand Down Expand Up @@ -79,5 +78,8 @@ if condition {
}
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add the traditional example of "when drop flags are useful":

let buf;
let result: &str = if let Some(name) = your_name {
    buf = format!("Hello, {}", name);
    &buf
} else {
    "Hello, World!"
};
use(result);
// `buf` is only initialized, and only dropped, on if `name` is Some.

The drop flags are tracked on the stack and no longer stashed in types that
implement drop.
At Rust 1.0, these flags were stored in the actual values that needed to
be tracked. This was a big mess and you had to worry about it in unsafe code.

As of Rust 1.13, these flags are stored seperately on the stack, so you no
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Starting from", rather than "As of". The flags will not go back into actual values in a way you have to care about.

longer need to worry about them.
Loading