|  | 
|  | 1 | +- Feature Name: Guaranteed non-static destructors | 
|  | 2 | +- Start Date: 2015-04-27 | 
|  | 3 | +- RFC PR: (leave this empty) | 
|  | 4 | +- Rust Issue: (leave this empty) | 
|  | 5 | + | 
|  | 6 | +# Summary | 
|  | 7 | + | 
|  | 8 | +Guarantee that the destructor for data that contains a borrow is run before any | 
|  | 9 | +code after the borrow’s lifetime is executed. | 
|  | 10 | + | 
|  | 11 | +# Motivation | 
|  | 12 | + | 
|  | 13 | +Rust is currently not guaranteed to run destructors on an object before | 
|  | 14 | +execution moves beyond its bounding-lifetime. This is surprising, | 
|  | 15 | +unintuitive, and leads to soundness mistakes. This is evidenced by two recently | 
|  | 16 | +approved API designs: `thread::scopped` and `drain_range`, both of which were | 
|  | 17 | +found to be unsound because they mistakenly assumed that their destructor would | 
|  | 18 | +run before any code outside of their bounding lifetimes could be executed. Even | 
|  | 19 | +if both are fixed, it is very likely that similar errors will arise in the | 
|  | 20 | +future. | 
|  | 21 | + | 
|  | 22 | +While we can try to continuously hammer home that leaks are not considered | 
|  | 23 | +`unsafe`, we'll never be able to prevent all such mistakes. It would be better | 
|  | 24 | +if we could actually provide the intuitive guarantee, which is what this RFC | 
|  | 25 | +attempts to accomplish. | 
|  | 26 | + | 
|  | 27 | +Note that this RFC is explicitly not attempting to solve all leaks, nor even to | 
|  | 28 | +ensure that all stack-anchored objects are destructed. Rust is primarily | 
|  | 29 | +concerned with memory safety, so this RFC tries to formulate a general rule | 
|  | 30 | +that addresses the issue of incorrect assumptions about destructors leading to | 
|  | 31 | +memory unsafety. It does not attempt to prevent the leaking of other resources, | 
|  | 32 | +as leaking a `'static` object is never unsafe. (`'static` objects are | 
|  | 33 | +conceptually safe to keep around “forever”, so any unsoundness that can be | 
|  | 34 | +obtained by a leak could also be obtained through other means). | 
|  | 35 | + | 
|  | 36 | +# Detailed design | 
|  | 37 | + | 
|  | 38 | +In addition to current guarantees, the following property can be relied upon: | 
|  | 39 | + | 
|  | 40 | +Given an object `A` implementing `Drop` whose lifetime is restricted to `'a` | 
|  | 41 | +(that is, the object may not live longer than `'a`), in absence of unsafe code, | 
|  | 42 | +the destructor for `A` is guaranteed to run before any code after the end of | 
|  | 43 | +`'a` is executed. | 
|  | 44 | + | 
|  | 45 | +This is already true at the language level. However, certain library constructs | 
|  | 46 | +currently allow safe code to break it. | 
|  | 47 | + | 
|  | 48 | +This has the following implications: | 
|  | 49 | + | 
|  | 50 | + * It is perfectly acceptable to forget, leak, et cetera any object as long as | 
|  | 51 | +   that object is valid for the duration of `'static`. Intuitively, this makes | 
|  | 52 | +   sense, as forgetting an object is the same as having it live forever. | 
|  | 53 | + * A resource that is never freed due to and endless loop, a deadlock, or | 
|  | 54 | +   program termination is valid because any code that comes after the end of | 
|  | 55 | +   `'a` is never executed. | 
|  | 56 | + * Code would be allowed to rely on the guarantee for soundness. This means | 
|  | 57 | +   that patterns such as `thread::scoped` and the initially-proposed | 
|  | 58 | +   `Vec::drain_range` would be sound. | 
|  | 59 | + * Unsafe code is free to forget objects as needed in cases where the | 
|  | 60 | +   programmer guarantees it is sound. However, it is not allowed to expose a | 
|  | 61 | +   safe interface that would allow the above guarantee to be violated. | 
|  | 62 | + | 
|  | 63 | +As noted, this guarantee is already true at the core language level. However, | 
|  | 64 | +it can be violated in safe code when using the standard library. There are | 
|  | 65 | +two known ways in which this can happen: when a destructor panics in certain | 
|  | 66 | +situations (e.g., in a `Vec`), and when a reference cycle is created using `Rc` | 
|  | 67 | +and friends. | 
|  | 68 | + | 
|  | 69 | +This RFC proposes the following solutions: | 
|  | 70 | + | 
|  | 71 | + * Specify that any panic that occurs while a destructor is in progress results | 
|  | 72 | +   in an abort. Panicking in a destructor is generally a bad idea with many | 
|  | 73 | +   edge cases, so this is probably desirable, anyway. It should be possible to | 
|  | 74 | +   implement this efficiently in a similar manner to C++’s `noexcept`. | 
|  | 75 | + * Restrict the basic `new` operation of existing reference-counted types to | 
|  | 76 | +   types valid for `'static`, which are always safe to leak. Additionally, | 
|  | 77 | +   introduce a scoped cycle collector that can be used to safely create `Rc`s | 
|  | 78 | +   with a shorter lifetime, and research the possibility of a reference-counted | 
|  | 79 | +   type that statically disallows cycles. | 
|  | 80 | + | 
|  | 81 | +Specifically, the scoped cycle collector would operate as follows: | 
|  | 82 | + | 
|  | 83 | + * There `RcGuard` type that can be instantiated with a given lifetime `'a`. | 
|  | 84 | + * Safe code can use the guard object to safely create `Rc`s with any type that | 
|  | 85 | +   outlives `'a`. | 
|  | 86 | + * When the `RcGuard` is dropped, it drops the data of all `Rc's` created with | 
|  | 87 | +   it. This would free any cycles. | 
|  | 88 | + * Attempting to dereference any `Rc` whose content had already been dropped | 
|  | 89 | +   (e.g., during cycle clean-up) would cause a panic (and thus an abort if it | 
|  | 90 | +   happened during RcGuard's destructor). | 
|  | 91 | + | 
|  | 92 | +The guard technique can also be applied to channels to ensure that they are | 
|  | 93 | +properly cleaned up even if the user does something like send the receiver and | 
|  | 94 | +sender into their own channel. | 
|  | 95 | + | 
|  | 96 | +# Drawbacks | 
|  | 97 | + | 
|  | 98 | + * Using `Rc`s for non-`'static` types will be slightly less convenient. The | 
|  | 99 | +   user will either have to use the cycle guard, an acyclic Rc type, or use | 
|  | 100 | +   `unsafe` and manually verify that no leaks are possible. (In the compiler, | 
|  | 101 | +   for instance, the guard would need to be somewhere outlived by the type | 
|  | 102 | +   arenas (perhaps in the `ctxt` object)). | 
|  | 103 | + * Probably not enough time to implement all of this by 1.0. This can be | 
|  | 104 | +   mitigated by implementing the minimum necessary to make this | 
|  | 105 | +   backward-compatible. Since the behavior of panicking destructors are already | 
|  | 106 | +   unspecified and subject to change, this would mean restricting the safe | 
|  | 107 | +   creation of reference-counted objects to containing `'static` values and | 
|  | 108 | +   adding an `unsafe` way to use shorter lifetimes. This would make using `Rc`s | 
|  | 109 | +   for non-`'static` data impossible without `unsafe` in 1.0, which is not | 
|  | 110 | +   ideal. | 
|  | 111 | + | 
|  | 112 | +# Alternatives | 
|  | 113 | + | 
|  | 114 | + * Don't consider failing to destruct non-`'static` data unsafe. | 
|  | 115 | + | 
|  | 116 | +   This is more or less the status quo, barring certain adjustments such as | 
|  | 117 | +   removing `unsafe` from `mem::forget`. | 
|  | 118 | + | 
|  | 119 | +   This would be unfortunate, as it makes a very common RAII pattern unusable | 
|  | 120 | +   for anything memory-safety related. Furthermore, it breaks expectations. It | 
|  | 121 | +   seems like using an RAII guard should be memory safe, to the point that two | 
|  | 122 | +   new APIs were recently designed that relied on it for soundness, despite the | 
|  | 123 | +   fact that leaks have not been considered unsafe for a long time. | 
|  | 124 | + | 
|  | 125 | +   It is very likely that people will continue to make this mistake (even if | 
|  | 126 | +   the core team doesn't, third party developers almost certainly will). Rust’s | 
|  | 127 | +   strong lifetime ownership semantics make it seem like something that should | 
|  | 128 | +   be reliable. This is compounded by the fact that it is true of the core | 
|  | 129 | +   language, and “true-enough” in practice that developers will continue to | 
|  | 130 | +   assume that it can be relied upon. | 
|  | 131 | + | 
|  | 132 | +   Even if the programmer is aware of the limitation, taking it into account | 
|  | 133 | +   can lead to more difficult and convoluted API design to ensure soundness. | 
|  | 134 | +   Given that they are *usually* reliable, a programmer my opt to go with an | 
|  | 135 | +   RAII design anyway to save time, reducing the value of Rust’s safety | 
|  | 136 | +   guarantees. | 
|  | 137 | + | 
|  | 138 | + * Leave `Rc` and friends. Add a `Leak` trait or similar. All APIs that can | 
|  | 139 | +   potentially leak (such as `Rc`) would have a `Leak` bound, and programmers | 
|  | 140 | +   who want to rely on their destructor being called before their lifetime ends | 
|  | 141 | +   would have to add a `Leak` bound to opt out of being usable with `Rc`s, | 
|  | 142 | +   channels, et cetera. | 
|  | 143 | + | 
|  | 144 | +   While `!Leak` would technically only be needed for types can that lead to | 
|  | 145 | +   unsoundness if their destructor were skipped, many would probably use it for | 
|  | 146 | +   any guard-like type whose destructor “should” run, preventing their use in | 
|  | 147 | +   certain contexts unnecessarily. | 
|  | 148 | + | 
|  | 149 | +   Additionally, it is likely that there will be types with *do* need the | 
|  | 150 | +   guarantees for memory safety that otherwise would be useful to keep in an | 
|  | 151 | +   `Rc` or send over a channel, which would not be possible. | 
|  | 152 | + | 
|  | 153 | +   Instead of defining a single, simple rule for all types, the `Leak` | 
|  | 154 | +   trait would have to be specifically dealt with all over, e.g., by adding | 
|  | 155 | +   `+Leak` to the bounds of anything that you want to put in an `Rc`. Also, if | 
|  | 156 | +   leaking `drain_range` or something similar is made to leak the referenced | 
|  | 157 | +   values instead of leading to unsoundness, it needs to be `Leak` if and only | 
|  | 158 | +   if the contained type is.  This adds complexity and mental burden. | 
|  | 159 | + | 
|  | 160 | +   Finally, while some of the solutions proposed in this RFC for handling | 
|  | 161 | +   non-`'static` data could be applied to `!Leak` data, the added complexity | 
|  | 162 | +   added by a `Leak` trait would be even less worth it if the complexity to | 
|  | 163 | +   work around it had to be added to `Rc`, channels, et cetera, anyway. | 
|  | 164 | + | 
|  | 165 | + * Guarantee that all stack-anchored objects have their destructor run if the | 
|  | 166 | +   program exits normally. | 
|  | 167 | + | 
|  | 168 | +   This is much more challenging, would require sweeping changes in many areas, | 
|  | 169 | +   and is not even that useful: you can always move a `'static` object off the | 
|  | 170 | +   stack into a static location. Also, since `'static` objects can conceptually | 
|  | 171 | +   live forever, there seems little benefit to attempting to enforce otherwise. | 
|  | 172 | + | 
|  | 173 | +   In addition, there several benefits to being able to being able to safely | 
|  | 174 | +   forget static data, such as forgetting a `File` to keep the file from being | 
|  | 175 | +   closed once you have transferred the underlying file descriptor. | 
|  | 176 | + | 
|  | 177 | +# Unresolved questions | 
|  | 178 | + | 
|  | 179 | +What are the best designs for safely allowing channels and `Rc`s to safely work | 
|  | 180 | +with non-`'static` data? | 
0 commit comments