|
| 1 | +- Feature Name: stable_drop_order |
| 2 | +- Start Date: 2017-01-19 |
| 3 | +- RFC PR: https://github.com/rust-lang/rfcs/pull/1857 |
| 4 | +- Rust Issue: https://github.com/rust-lang/rust/issues/43034 |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +I propose we specify and stabilize drop order in Rust, instead of treating |
| 10 | +it as an implementation detail. The stable drop order should be based on the |
| 11 | +current implementation. This results in avoiding breakage and still allows |
| 12 | +alternative, opt-in, drop orders to be introduced in the future. |
| 13 | + |
| 14 | +# Motivation |
| 15 | +[motivation]: #motivation |
| 16 | + |
| 17 | +After lots of discussion on [issue 744](https://github.com/rust-lang/rfcs/issues/744), |
| 18 | +there seems to be consensus about the need for a stable drop order. See, for instance, |
| 19 | +[this](https://github.com/rust-lang/rfcs/issues/744#issuecomment-231215181) and |
| 20 | +[this](https://github.com/rust-lang/rfcs/issues/744#issuecomment-231237499) comment. |
| 21 | + |
| 22 | +The current drop order seems counter-intuitive (fields are dropped in FIFO order |
| 23 | +instead of LIFO), but changing it would inevitably result in breakage. There have |
| 24 | +been cases in the recent past when code broke because of people relying on unspecified |
| 25 | +behavior (see for instance the |
| 26 | +[post](https://internals.rust-lang.org/t/rolling-out-or-unrolling-struct-field-reorderings/4485) |
| 27 | +about struct field reorderings). It is highly probable that similar breakage |
| 28 | +would result from changes to the drop order. See for instance, the |
| 29 | +[comment](https://github.com/rust-lang/rfcs/issues/744#issuecomment-225918642) |
| 30 | +from @sfackler, which reflects the problems that would arise: |
| 31 | + |
| 32 | +> Real code in the wild does rely on the current drop order, including rust-openssl, |
| 33 | +and *there is no upgrade path* if we reverse it. Old versions of the libraries will |
| 34 | +be subtly broken when compiled with new rustc, and new versions of the libraries |
| 35 | +will be broken when compiled with old rustc. |
| 36 | + |
| 37 | +Introducing a new drop order without breaking things would require figuring out how to: |
| 38 | + |
| 39 | +* Forbid an old compiler (with the old drop order) from compiling recent Rust |
| 40 | +code (which could rely on the new drop order). |
| 41 | +* Let the new compiler (with the new drop order) recognize old Rust code |
| 42 | +(which could rely on the old drop order). This way it could choose to either: |
| 43 | +(a) fail to compile; or (b) compile using the old drop order. |
| 44 | + |
| 45 | +Both requirements seem quite difficult, if not impossible, to meet. Even in case |
| 46 | +we figured out how to meet those requirements, the complexity of the approach would |
| 47 | +probably outweight the current complexity of having a non-intuitive drop order. |
| 48 | + |
| 49 | +Finally, in case people really dislike the current drop order, it may still |
| 50 | +be possible to introduce alternative, opt-in, drop orders in a backwards |
| 51 | +compatible way. However, that is not covered in this RFC. |
| 52 | + |
| 53 | +# Detailed design |
| 54 | +[design]: #detailed-design |
| 55 | + |
| 56 | +The design is the same as currently implemented in rustc and is described |
| 57 | +below. This behavior will be enforced by run-pass tests. |
| 58 | + |
| 59 | +### Tuples, structs and enum variants |
| 60 | + |
| 61 | +Struct fields are dropped in the same order as they are declared. Consider, |
| 62 | +for instance, the struct below: |
| 63 | + |
| 64 | +```rust |
| 65 | +struct Foo { |
| 66 | + bar: String, |
| 67 | + baz: String, |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +In this case, `bar` will be the first field to be destroyed, followed by `baz`. |
| 72 | + |
| 73 | +Tuples and tuple structs show the same behavior, as well as enum variants of both kinds |
| 74 | +(struct and tuple variants). |
| 75 | + |
| 76 | +Note that a panic during construction of one of previous data structures causes |
| 77 | +destruction in a different order. Since the object has not yet been constructed, |
| 78 | +its fields are treated as local variables (which are destroyed in LIFO order). |
| 79 | +See the example below: |
| 80 | + |
| 81 | +```rust |
| 82 | +let x = MyStruct { |
| 83 | + field1: String::new(), |
| 84 | + field2: String::new(), |
| 85 | + field3: panic!() |
| 86 | +}; |
| 87 | +``` |
| 88 | + |
| 89 | +In this case, `field2` is destructed first and `field1` second, which may |
| 90 | +seem counterintuitive at first but makes sense when you consider that the |
| 91 | +initialized fields are actually temporary variables. Note that the drop order |
| 92 | +depends on the order of the fields in the *initializer* and not in the struct |
| 93 | +declaration. |
| 94 | + |
| 95 | +### Slices and Vec |
| 96 | + |
| 97 | +Slices and vectors show the same behavior as structs and enums. This behavior |
| 98 | +can be illustrated by the code below, where the first elements are dropped |
| 99 | +first. |
| 100 | + |
| 101 | +```rust |
| 102 | +for x in xs { drop(x) } |
| 103 | +``` |
| 104 | + |
| 105 | +If there is a panic during construction of the slice or the `Vec`, the |
| 106 | +drop order is reversed (that is, when using `[]` literals or the `vec![]` macro). |
| 107 | +Consider the following example: |
| 108 | + |
| 109 | +```rust |
| 110 | +let xs = [X, Y, panic!()]; |
| 111 | +``` |
| 112 | + |
| 113 | +Here, `Y` will be dropped first and `X` second. |
| 114 | + |
| 115 | +### Allowed unspecified behavior |
| 116 | + |
| 117 | +Besides the previous constructs, there are other ones that do not need |
| 118 | +a stable drop order (at least, there is not yet evidence that it would be |
| 119 | +useful). It is the case of `vec![expr; n]` and closure captures. |
| 120 | + |
| 121 | +Vectors initialized with `vec![expr; n]` syntax clone the value of `expr` |
| 122 | +in order to fill the vector. In case `clone` panics, the values produced so far |
| 123 | +are dropped in unspecified order. The order is closely tied to an implementation |
| 124 | +detail and the benefits of stabilizing it seem small. It is difficult to come |
| 125 | +up with a real-world scenario where the drop order of cloned objects is relevant |
| 126 | +to ensure some kind of invariant. Furthermore, we may want to modify the implementation |
| 127 | +in the future. |
| 128 | + |
| 129 | +Closure captures are also dropped in unspecified order. At this moment, it seems |
| 130 | +like the drop order is similar to the order in which the captures are consumed within |
| 131 | +the closure (see [this blog post](https://aochagavia.github.io/blog/exploring-rusts-unspecified-drop-order/) |
| 132 | +for more details). Again, this order is closely tied to an implementation that |
| 133 | +we may want to change in the future, and the benefits of stabilizing it seem small. |
| 134 | +Furthermore, enforcing invariants through closure captures seems like a terrible footgun |
| 135 | +at best (the same effect can be achieved with much less obscure methods, like passing |
| 136 | +a struct as an argument). |
| 137 | + |
| 138 | +Note: we ignore slices initialized with `[expr; n]` syntax, since they may only |
| 139 | +contain `Copy` types, which in turn cannot implement `Drop`. |
| 140 | + |
| 141 | +# How We Teach This |
| 142 | +[how-we-teach-this]: #how-we-teach-this |
| 143 | + |
| 144 | +When mentioning destructors in the Rust book, Reference and other documentation, |
| 145 | +we should also mention the overall picture for a type that implements `Drop`. |
| 146 | +In particular, if a `struct`/`enum` implements Drop, then when it is dropped we will |
| 147 | +first execute the user's code and then drop all the fields (in the given order). Thus |
| 148 | +any code in `Drop` must leave the fields in an initialized state such that they can |
| 149 | +be dropped. If you wish to interleave the fields being dropped and user code being |
| 150 | +executed, you can make the fields into `Option` and have a custom drop that calls take() |
| 151 | +(or else wrap your type in a union with a single member and implement `Drop` such that |
| 152 | +it invokes `ptr::read()` or something similar). |
| 153 | + |
| 154 | +It is also important to mention that `union` types never drop their contents. |
| 155 | + |
| 156 | +# Drawbacks |
| 157 | +[drawbacks]: #drawbacks |
| 158 | + |
| 159 | +* The counter-intuitive drop order is here to stay. |
| 160 | + |
| 161 | +# Alternatives |
| 162 | +[alternatives]: #alternatives |
| 163 | + |
| 164 | +* Figure out how to let rustc know the language version targeted by a given program. |
| 165 | +This way we could introduce a new drop order without breaking code. |
| 166 | +* Introduce a new drop order anyway, try to minimize breakage by running crater |
| 167 | +and hope for the best. |
| 168 | + |
| 169 | +# Unresolved questions |
| 170 | +[unresolved]: #unresolved-questions |
| 171 | + |
| 172 | +* Where do we draw the line between the constructs where drop order should be stabilized |
| 173 | +and the rest? Should the drop order of closure captures be specified? And the drop order |
| 174 | +of `vec![expr; n]`? |
0 commit comments