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