Skip to content

Commit c02e0e7

Browse files
authored
Merge pull request #113 from vorner/defined-drop-order
dropck: The drop order is now defined
2 parents f1ff93b + 1da7d38 commit c02e0e7

File tree

1 file changed

+99
-48
lines changed

1 file changed

+99
-48
lines changed

src/dropck.md

Lines changed: 99 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,46 +22,51 @@ let y;
2222
}
2323
```
2424

25-
Each creates its own scope, clearly establishing that one drops before the
26-
other. However, what if we do the following?
25+
There are some more complex situations which are not possible to desugar using
26+
scopes, but the order is still defined ‒ variables are dropped in the reverse
27+
order of their definition, fields of structs and tuples in order of their
28+
definition. There are some more details about order of drop in [rfc1875].
29+
30+
Let's do this:
2731

2832
```rust,ignore
29-
let (x, y) = (vec![], vec![]);
33+
let tuple = (vec![], vec![]);
3034
```
3135

32-
Does either value strictly outlive the other? The answer is in fact *no*,
33-
neither value strictly outlives the other. Of course, one of x or y will be
34-
dropped before the other, but the actual order is not specified. Tuples aren't
35-
special in this regard; composite structures just don't guarantee their
36-
destruction order as of Rust 1.0.
37-
38-
We *could* specify this for the fields of built-in composites like tuples and
39-
structs. However, what about something like Vec? Vec has to manually drop its
40-
elements via pure-library code. In general, anything that implements Drop has
41-
a chance to fiddle with its innards during its final death knell. Therefore
42-
the compiler can't sufficiently reason about the actual destruction order
43-
of the contents of any type that implements Drop.
36+
The left vector is dropped first. But does it mean the right one strictly
37+
outlives it in the eyes of the borrow checker? The answer to this question is
38+
*no*. The borrow checker could track fields of tuples separately, but it would
39+
still be unable to decide what outlives what in case of vector elements, which
40+
are dropped manually via pure-library code the borrow checker doesn't
41+
understand.
4442

4543
So why do we care? We care because if the type system isn't careful, it could
4644
accidentally make dangling pointers. Consider the following simple program:
4745

4846
```rust
4947
struct Inspector<'a>(&'a u8);
5048

49+
struct World<'a> {
50+
inspector: Option<Inspector<'a>>,
51+
days: Box<u8>,
52+
}
53+
5154
fn main() {
52-
let (inspector, days);
53-
days = Box::new(1);
54-
inspector = Inspector(&days);
55+
let mut world = World {
56+
inspector: None,
57+
days: Box::new(1),
58+
};
59+
world.inspector = Some(Inspector(&world.days));
5560
}
5661
```
5762

58-
This program is totally sound and compiles today. The fact that `days` does
59-
not *strictly* outlive `inspector` doesn't matter. As long as the `inspector`
60-
is alive, so is days.
63+
This program is totally sound and compiles today. The fact that `days` does not
64+
strictly outlive `inspector` doesn't matter. As long as the `inspector` is
65+
alive, so is `days`.
6166

6267
However if we add a destructor, the program will no longer compile!
6368

64-
```rust,ignore
69+
```rust,compile_fail
6570
struct Inspector<'a>(&'a u8);
6671
6772
impl<'a> Drop for Inspector<'a> {
@@ -70,30 +75,38 @@ impl<'a> Drop for Inspector<'a> {
7075
}
7176
}
7277
78+
struct World<'a> {
79+
inspector: Option<Inspector<'a>>,
80+
days: Box<u8>,
81+
}
82+
7383
fn main() {
74-
let (inspector, days);
75-
days = Box::new(1);
76-
inspector = Inspector(&days);
84+
let mut world = World {
85+
inspector: None,
86+
days: Box::new(1),
87+
};
88+
world.inspector = Some(Inspector(&world.days));
7789
// Let's say `days` happens to get dropped first.
7890
// Then when Inspector is dropped, it will try to read free'd memory!
7991
}
8092
```
8193

8294
```text
83-
error[E0597]: `days` does not live long enough
84-
--> src/main.rs:12:28
95+
error[E0597]: `world.days` does not live long enough
96+
--> src/main.rs:20:39
8597
|
86-
12 | inspector = Inspector(&days);
87-
| ^^^^ borrowed value does not live long enough
98+
20 | world.inspector = Some(Inspector(&world.days));
99+
| ^^^^^^^^^^ borrowed value does not live long enough
88100
...
89-
15 | }
90-
| - `days` dropped here while still borrowed
101+
23 | }
102+
| - `world.days` dropped here while still borrowed
91103
|
92104
= note: values in a scope are dropped in the opposite order they are created
93-
94-
error: aborting due to previous error
95105
```
96106

107+
You can try changing the order of fields or use a tuple instead of the struct,
108+
it'll still not compile.
109+
97110
Implementing `Drop` lets the `Inspector` execute some arbitrary code during its
98111
death. This means it can potentially observe that types that are supposed to
99112
live as long as it does actually were destroyed first.
@@ -116,12 +129,14 @@ sound to drop.
116129

117130
The reason that it is not always necessary to satisfy the above rule
118131
is that some Drop implementations will not access borrowed data even
119-
though their type gives them the capability for such access.
132+
though their type gives them the capability for such access, or because we know
133+
the specific drop order and the borrowed data is still fine even if the borrow
134+
checker doesn't know that.
120135

121136
For example, this variant of the above `Inspector` example will never
122137
access borrowed data:
123138

124-
```rust,ignore
139+
```rust,compile_fail
125140
struct Inspector<'a>(&'a u8, &'static str);
126141
127142
impl<'a> Drop for Inspector<'a> {
@@ -130,10 +145,17 @@ impl<'a> Drop for Inspector<'a> {
130145
}
131146
}
132147
148+
struct World<'a> {
149+
inspector: Option<Inspector<'a>>,
150+
days: Box<u8>,
151+
}
152+
133153
fn main() {
134-
let (inspector, days);
135-
days = Box::new(1);
136-
inspector = Inspector(&days, "gadget");
154+
let mut world = World {
155+
inspector: None,
156+
days: Box::new(1),
157+
};
158+
world.inspector = Some(Inspector(&world.days, "gadget"));
137159
// Let's say `days` happens to get dropped first.
138160
// Even when Inspector is dropped, its destructor will not access the
139161
// borrowed `days`.
@@ -142,21 +164,26 @@ fn main() {
142164

143165
Likewise, this variant will also never access borrowed data:
144166

145-
```rust,ignore
146-
use std::fmt;
147-
148-
struct Inspector<T: fmt::Display>(T, &'static str);
167+
```rust,compile_fail
168+
struct Inspector<T>(T, &'static str);
149169
150-
impl<T: fmt::Display> Drop for Inspector<T> {
170+
impl<T> Drop for Inspector<T> {
151171
fn drop(&mut self) {
152172
println!("Inspector(_, {}) knows when *not* to inspect.", self.1);
153173
}
154174
}
155175
176+
struct World<T> {
177+
inspector: Option<Inspector<T>>,
178+
days: Box<u8>,
179+
}
180+
156181
fn main() {
157-
let (inspector, days): (Inspector<&u8>, Box<u8>);
158-
days = Box::new(1);
159-
inspector = Inspector(&days, "gadget");
182+
let mut world = World {
183+
inspector: None,
184+
days: Box::new(1),
185+
};
186+
world.inspector = Some(Inspector(&world.days, "gadget"));
160187
// Let's say `days` happens to get dropped first.
161188
// Even when Inspector is dropped, its destructor will not access the
162189
// borrowed `days`.
@@ -194,16 +221,31 @@ not access any expired data, even if its type gives it the capability
194221
to do so.
195222

196223
That attribute is called `may_dangle` and was introduced in [RFC 1327][rfc1327].
197-
To deploy it on the `Inspector` example from above, we would write:
224+
To deploy it on the `Inspector` from above, we would write:
225+
226+
```rust
227+
#![feature(dropck_eyepatch)]
198228

199-
```rust,ignore
200229
struct Inspector<'a>(&'a u8, &'static str);
201230

202231
unsafe impl<#[may_dangle] 'a> Drop for Inspector<'a> {
203232
fn drop(&mut self) {
204233
println!("Inspector(_, {}) knows when *not* to inspect.", self.1);
205234
}
206235
}
236+
237+
struct World<'a> {
238+
days: Box<u8>,
239+
inspector: Option<Inspector<'a>>,
240+
}
241+
242+
fn main() {
243+
let mut world = World {
244+
inspector: None,
245+
days: Box::new(1),
246+
};
247+
world.inspector = Some(Inspector(&world.days, "gatget"));
248+
}
207249
```
208250

209251
Use of this attribute requires the `Drop` impl to be marked `unsafe` because the
@@ -279,11 +321,20 @@ attribute makes the type vulnerable to misuse that the borrower
279321
checker will not catch, inviting havoc. It is better to avoid adding
280322
the attribute.
281323

324+
# A related side note about drop order
325+
326+
While the drop order of fields inside a struct is defined, relying on it is
327+
fragile and subtle. When the order matters, it is better to use the
328+
[`ManuallyDrop`] wrapper.
329+
282330
# Is that all about drop checker?
283331

284332
It turns out that when writing unsafe code, we generally don't need to
285333
worry at all about doing the right thing for the drop checker. However there
286334
is one special case that you need to worry about, which we will look at in
287335
the next section.
288336

337+
289338
[rfc1327]: https://github.com/rust-lang/rfcs/blob/master/text/1327-dropck-param-eyepatch.md
339+
[rfc1857]: https://github.com/rust-lang/rfcs/blob/master/text/1857-stabilize-drop-order.md
340+
[`ManuallyDrop`]: ../std/mem/struct.ManuallyDrop.html

0 commit comments

Comments
 (0)