]>
Commit | Line | Data |
---|---|---|
c1a9b12d SL |
1 | % Destructors |
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 | |
92a42be0 | 29 | #![feature(alloc, heap_api, drop_in_place, unique)] |
e9174d1e SL |
30 | |
31 | extern crate alloc; | |
c1a9b12d | 32 | |
92a42be0 | 33 | use std::ptr::{drop_in_place, Unique}; |
c1a9b12d SL |
34 | use std::mem; |
35 | ||
e9174d1e SL |
36 | use alloc::heap; |
37 | ||
c1a9b12d SL |
38 | struct Box<T>{ ptr: Unique<T> } |
39 | ||
40 | impl<T> Drop for Box<T> { | |
41 | fn drop(&mut self) { | |
42 | unsafe { | |
43 | drop_in_place(*self.ptr); | |
44 | heap::deallocate((*self.ptr) as *mut u8, | |
45 | mem::size_of::<T>(), | |
46 | mem::align_of::<T>()); | |
47 | } | |
48 | } | |
49 | } | |
e9174d1e | 50 | # fn main() {} |
c1a9b12d SL |
51 | ``` |
52 | ||
53 | and this works fine because when Rust goes to drop the `ptr` field it just sees | |
e9174d1e | 54 | a [Unique] that has no actual `Drop` implementation. Similarly nothing can |
b039eaaf | 55 | use-after-free the `ptr` because when drop exits, it becomes inaccessible. |
c1a9b12d SL |
56 | |
57 | However this wouldn't work: | |
58 | ||
59 | ```rust | |
92a42be0 | 60 | #![feature(alloc, heap_api, drop_in_place, unique)] |
e9174d1e SL |
61 | |
62 | extern crate alloc; | |
c1a9b12d | 63 | |
92a42be0 | 64 | use std::ptr::{drop_in_place, Unique}; |
c1a9b12d SL |
65 | use std::mem; |
66 | ||
e9174d1e SL |
67 | use alloc::heap; |
68 | ||
c1a9b12d SL |
69 | struct Box<T>{ ptr: Unique<T> } |
70 | ||
71 | impl<T> Drop for Box<T> { | |
72 | fn drop(&mut self) { | |
73 | unsafe { | |
74 | drop_in_place(*self.ptr); | |
75 | heap::deallocate((*self.ptr) as *mut u8, | |
76 | mem::size_of::<T>(), | |
77 | mem::align_of::<T>()); | |
78 | } | |
79 | } | |
80 | } | |
81 | ||
82 | struct SuperBox<T> { my_box: Box<T> } | |
83 | ||
84 | impl<T> Drop for SuperBox<T> { | |
85 | fn drop(&mut self) { | |
86 | unsafe { | |
87 | // Hyper-optimized: deallocate the box's contents for it | |
88 | // without `drop`ing the contents | |
89 | heap::deallocate((*self.my_box.ptr) as *mut u8, | |
90 | mem::size_of::<T>(), | |
91 | mem::align_of::<T>()); | |
92 | } | |
93 | } | |
94 | } | |
e9174d1e | 95 | # fn main() {} |
c1a9b12d SL |
96 | ``` |
97 | ||
98 | After we deallocate the `box`'s ptr in SuperBox's destructor, Rust will | |
99 | happily proceed to tell the box to Drop itself and everything will blow up with | |
100 | use-after-frees and double-frees. | |
101 | ||
b039eaaf | 102 | Note that the recursive drop behavior applies to all structs and enums |
c1a9b12d SL |
103 | regardless of whether they implement Drop. Therefore something like |
104 | ||
105 | ```rust | |
106 | struct Boxy<T> { | |
107 | data1: Box<T>, | |
108 | data2: Box<T>, | |
109 | info: u32, | |
110 | } | |
111 | ``` | |
112 | ||
113 | will have its data1 and data2's fields destructors whenever it "would" be | |
114 | dropped, even though it itself doesn't implement Drop. We say that such a type | |
115 | *needs Drop*, even though it is not itself Drop. | |
116 | ||
117 | Similarly, | |
118 | ||
119 | ```rust | |
120 | enum Link { | |
121 | Next(Box<Link>), | |
122 | None, | |
123 | } | |
124 | ``` | |
125 | ||
126 | will have its inner Box field dropped if and only if an instance stores the | |
127 | Next variant. | |
128 | ||
e9174d1e | 129 | In general this works really nicely because you don't need to worry about |
c1a9b12d SL |
130 | adding/removing drops when you refactor your data layout. Still there's |
131 | certainly many valid usecases for needing to do trickier things with | |
132 | destructors. | |
133 | ||
134 | The classic safe solution to overriding recursive drop and allowing moving out | |
135 | of Self during `drop` is to use an Option: | |
136 | ||
137 | ```rust | |
92a42be0 | 138 | #![feature(alloc, heap_api, drop_in_place, unique)] |
e9174d1e SL |
139 | |
140 | extern crate alloc; | |
c1a9b12d | 141 | |
92a42be0 | 142 | use std::ptr::{drop_in_place, Unique}; |
c1a9b12d SL |
143 | use std::mem; |
144 | ||
e9174d1e SL |
145 | use alloc::heap; |
146 | ||
c1a9b12d SL |
147 | struct Box<T>{ ptr: Unique<T> } |
148 | ||
149 | impl<T> Drop for Box<T> { | |
150 | fn drop(&mut self) { | |
151 | unsafe { | |
152 | drop_in_place(*self.ptr); | |
153 | heap::deallocate((*self.ptr) as *mut u8, | |
154 | mem::size_of::<T>(), | |
155 | mem::align_of::<T>()); | |
156 | } | |
157 | } | |
158 | } | |
159 | ||
160 | struct SuperBox<T> { my_box: Option<Box<T>> } | |
161 | ||
162 | impl<T> Drop for SuperBox<T> { | |
163 | fn drop(&mut self) { | |
164 | unsafe { | |
165 | // Hyper-optimized: deallocate the box's contents for it | |
166 | // without `drop`ing the contents. Need to set the `box` | |
167 | // field as `None` to prevent Rust from trying to Drop it. | |
168 | let my_box = self.my_box.take().unwrap(); | |
169 | heap::deallocate((*my_box.ptr) as *mut u8, | |
170 | mem::size_of::<T>(), | |
171 | mem::align_of::<T>()); | |
172 | mem::forget(my_box); | |
173 | } | |
174 | } | |
175 | } | |
e9174d1e | 176 | # fn main() {} |
c1a9b12d SL |
177 | ``` |
178 | ||
179 | However this has fairly odd semantics: you're saying that a field that *should* | |
180 | always be Some *may* be None, just because that happens in the destructor. Of | |
181 | course this conversely makes a lot of sense: you can call arbitrary methods on | |
182 | self during the destructor, and this should prevent you from ever doing so after | |
183 | deinitializing the field. Not that it will prevent you from producing any other | |
184 | arbitrarily invalid state in there. | |
185 | ||
186 | On balance this is an ok choice. Certainly what you should reach for by default. | |
187 | However, in the future we expect there to be a first-class way to announce that | |
188 | a field shouldn't be automatically dropped. | |
189 | ||
190 | [Unique]: phantom-data.html |