]>
Commit | Line | Data |
---|---|---|
8bb4bdeb | 1 | # Destructors |
c1a9b12d SL |
2 | |
3 | What the language *does* provide is full-blown automatic destructors through the | |
4 | `Drop` trait, which provides the following method: | |
5 | ||
6 | ```rust,ignore | |
7 | fn drop(&mut self); | |
8 | ``` | |
9 | ||
10 | This method gives the type time to somehow finish what it was doing. | |
11 | ||
12 | **After `drop` is run, Rust will recursively try to drop all of the fields | |
13 | of `self`.** | |
14 | ||
15 | This is a convenience feature so that you don't have to write "destructor | |
16 | boilerplate" to drop children. If a struct has no special logic for being | |
17 | dropped other than dropping its children, then it means `Drop` doesn't need to | |
18 | be implemented at all! | |
19 | ||
b039eaaf | 20 | **There is no stable way to prevent this behavior in Rust 1.0.** |
c1a9b12d SL |
21 | |
22 | Note that taking `&mut self` means that even if you could suppress recursive | |
23 | Drop, Rust will prevent you from e.g. moving fields out of self. For most types, | |
24 | this is totally fine. | |
25 | ||
26 | For instance, a custom implementation of `Box` might write `Drop` like this: | |
27 | ||
28 | ```rust | |
0531ce1d | 29 | #![feature(ptr_internals, allocator_api, unique)] |
e9174d1e | 30 | |
94b46f34 | 31 | use std::alloc::{Alloc, Global, GlobalAlloc, Layout}; |
c1a9b12d | 32 | use std::mem; |
94b46f34 | 33 | use std::ptr::{drop_in_place, NonNull, Unique}; |
e9174d1e | 34 | |
c1a9b12d SL |
35 | struct Box<T>{ ptr: Unique<T> } |
36 | ||
37 | impl<T> Drop for Box<T> { | |
38 | fn drop(&mut self) { | |
39 | unsafe { | |
7cac9316 | 40 | drop_in_place(self.ptr.as_ptr()); |
94b46f34 XL |
41 | let c: NonNull<T> = self.ptr.into(); |
42 | Global.dealloc(c.cast(), Layout::new::<T>()) | |
c1a9b12d SL |
43 | } |
44 | } | |
45 | } | |
e9174d1e | 46 | # fn main() {} |
c1a9b12d SL |
47 | ``` |
48 | ||
49 | and this works fine because when Rust goes to drop the `ptr` field it just sees | |
e9174d1e | 50 | a [Unique] that has no actual `Drop` implementation. Similarly nothing can |
b039eaaf | 51 | use-after-free the `ptr` because when drop exits, it becomes inaccessible. |
c1a9b12d SL |
52 | |
53 | However this wouldn't work: | |
54 | ||
55 | ```rust | |
0531ce1d | 56 | #![feature(allocator_api, ptr_internals, unique)] |
c1a9b12d | 57 | |
94b46f34 XL |
58 | use std::alloc::{Alloc, Global, GlobalAlloc, Layout}; |
59 | use std::ptr::{drop_in_place, Unique, NonNull}; | |
c1a9b12d SL |
60 | use std::mem; |
61 | ||
62 | struct Box<T>{ ptr: Unique<T> } | |
63 | ||
64 | impl<T> Drop for Box<T> { | |
65 | fn drop(&mut self) { | |
66 | unsafe { | |
7cac9316 | 67 | drop_in_place(self.ptr.as_ptr()); |
94b46f34 XL |
68 | let c: NonNull<T> = self.ptr.into(); |
69 | Global.dealloc(c.cast(), Layout::new::<T>()); | |
c1a9b12d SL |
70 | } |
71 | } | |
72 | } | |
73 | ||
74 | struct SuperBox<T> { my_box: Box<T> } | |
75 | ||
76 | impl<T> Drop for SuperBox<T> { | |
77 | fn drop(&mut self) { | |
78 | unsafe { | |
79 | // Hyper-optimized: deallocate the box's contents for it | |
80 | // without `drop`ing the contents | |
94b46f34 XL |
81 | let c: NonNull<T> = self.my_box.ptr.into(); |
82 | Global.dealloc(c.cast::<u8>(), Layout::new::<T>()); | |
c1a9b12d SL |
83 | } |
84 | } | |
85 | } | |
e9174d1e | 86 | # fn main() {} |
c1a9b12d SL |
87 | ``` |
88 | ||
89 | After we deallocate the `box`'s ptr in SuperBox's destructor, Rust will | |
90 | happily proceed to tell the box to Drop itself and everything will blow up with | |
91 | use-after-frees and double-frees. | |
92 | ||
b039eaaf | 93 | Note that the recursive drop behavior applies to all structs and enums |
c1a9b12d SL |
94 | regardless of whether they implement Drop. Therefore something like |
95 | ||
96 | ```rust | |
97 | struct Boxy<T> { | |
98 | data1: Box<T>, | |
99 | data2: Box<T>, | |
100 | info: u32, | |
101 | } | |
102 | ``` | |
103 | ||
104 | will have its data1 and data2's fields destructors whenever it "would" be | |
105 | dropped, even though it itself doesn't implement Drop. We say that such a type | |
106 | *needs Drop*, even though it is not itself Drop. | |
107 | ||
108 | Similarly, | |
109 | ||
110 | ```rust | |
111 | enum Link { | |
112 | Next(Box<Link>), | |
113 | None, | |
114 | } | |
115 | ``` | |
116 | ||
117 | will have its inner Box field dropped if and only if an instance stores the | |
118 | Next variant. | |
119 | ||
e9174d1e | 120 | In general this works really nicely because you don't need to worry about |
c1a9b12d SL |
121 | adding/removing drops when you refactor your data layout. Still there's |
122 | certainly many valid usecases for needing to do trickier things with | |
123 | destructors. | |
124 | ||
125 | The classic safe solution to overriding recursive drop and allowing moving out | |
126 | of Self during `drop` is to use an Option: | |
127 | ||
128 | ```rust | |
0531ce1d | 129 | #![feature(allocator_api, ptr_internals, unique)] |
c1a9b12d | 130 | |
94b46f34 XL |
131 | use std::alloc::{Alloc, GlobalAlloc, Global, Layout}; |
132 | use std::ptr::{drop_in_place, Unique, NonNull}; | |
c1a9b12d SL |
133 | use std::mem; |
134 | ||
135 | struct Box<T>{ ptr: Unique<T> } | |
136 | ||
137 | impl<T> Drop for Box<T> { | |
138 | fn drop(&mut self) { | |
139 | unsafe { | |
7cac9316 | 140 | drop_in_place(self.ptr.as_ptr()); |
94b46f34 XL |
141 | let c: NonNull<T> = self.ptr.into(); |
142 | Global.dealloc(c.cast(), Layout::new::<T>()); | |
c1a9b12d SL |
143 | } |
144 | } | |
145 | } | |
146 | ||
147 | struct SuperBox<T> { my_box: Option<Box<T>> } | |
148 | ||
149 | impl<T> Drop for SuperBox<T> { | |
150 | fn drop(&mut self) { | |
151 | unsafe { | |
152 | // Hyper-optimized: deallocate the box's contents for it | |
153 | // without `drop`ing the contents. Need to set the `box` | |
154 | // field as `None` to prevent Rust from trying to Drop it. | |
155 | let my_box = self.my_box.take().unwrap(); | |
94b46f34 XL |
156 | let c: NonNull<T> = my_box.ptr.into(); |
157 | Global.dealloc(c.cast(), Layout::new::<T>()); | |
c1a9b12d SL |
158 | mem::forget(my_box); |
159 | } | |
160 | } | |
161 | } | |
e9174d1e | 162 | # fn main() {} |
c1a9b12d SL |
163 | ``` |
164 | ||
165 | However this has fairly odd semantics: you're saying that a field that *should* | |
166 | always be Some *may* be None, just because that happens in the destructor. Of | |
167 | course this conversely makes a lot of sense: you can call arbitrary methods on | |
168 | self during the destructor, and this should prevent you from ever doing so after | |
169 | deinitializing the field. Not that it will prevent you from producing any other | |
170 | arbitrarily invalid state in there. | |
171 | ||
172 | On balance this is an ok choice. Certainly what you should reach for by default. | |
173 | However, in the future we expect there to be a first-class way to announce that | |
174 | a field shouldn't be automatically dropped. | |
175 | ||
176 | [Unique]: phantom-data.html |