|
| 1 | +# Eager drop design note |
| 2 | + |
| 3 | +- Project proposal [rust-lang/lang-team#86](https://github.com/rust-lang/lang-team/issues/86) |
| 4 | + |
| 5 | +## Observations |
| 6 | + |
| 7 | +### Any attempt to make drop run more eagerly will have to take borrows into account |
| 8 | + |
| 9 | +The original proposal was to use "CFG dead" as a criteria, but it's pretty clear that this will not work well. Example: |
| 10 | + |
| 11 | +```rust= |
| 12 | +{ |
| 13 | + let x = String::new(); |
| 14 | + let y = &x; |
| 15 | + // last use of x is here |
| 16 | + println!("{}", y); |
| 17 | + // but we use y here |
| 18 | + ... |
| 19 | +} |
| 20 | +``` |
| 21 | + |
| 22 | +Here, the fact that `y` (indirectly) uses `x` feels like an important thing to take into account. |
| 23 | + |
| 24 | +### Some destructors can be run "at any time"... |
| 25 | + |
| 26 | +Some destructors have very significant side-effects. The most notable example is dropping a lock guard. |
| 27 | + |
| 28 | +Others correspond solely to "releasing resources": freeing memory is the most common example, but another might be replacing an entry in a table because you are done using it. |
| 29 | + |
| 30 | +### ...but sometimes that significance is only known at the call site |
| 31 | + |
| 32 | +However, it can be hard to know what is significant. For a lock guard, for example, if the lock is just being used to guard the data, then moving the lock release early is actually _desirable_, because you want to release the lock as soon as you are doing changing the data. But sometimes you have a `Mutex<()>`, in which case the lock has extra semantics. It's hard to know for sure. |
| 33 | + |
| 34 | +### Smarter drop placement will mean that adding uses of a variable changes when its destructor runs |
| 35 | + |
| 36 | +This is not necesarily a problem, but it's an obvious implication: right now, the drop always runs when we exit the scope, so adding further uses to a variable has no effect, but that would have to change. That could be surprising (e.g., adding a debug printout changes the time when a lock is released). |
| 37 | + |
| 38 | +In contrast, if you add an early drop `drop(foo)` today, you get helpful error messages when you try to use it again. |
| 39 | + |
| 40 | +In other words, it's useful to have the _destructor_ occurring at a known time (sometimes...). |
| 41 | + |
| 42 | +### Today's drop rules are, however, a source of confusion |
| 43 | + |
| 44 | +The semantics of `let _ = <expr>` have been known to caught a lot of confusion, particularly given the interaction of place expressions and value expresssions: |
| 45 | + |
| 46 | +- `let _ = foo` -- no effect |
| 47 | +- `let _ = foo()` -- immediately drops the result of invoking `foo()` |
| 48 | +- `let _guard = foo` -- moves `foo` into `_guard` and drops at the end of the block |
| 49 | +- `let _guard = foo()` -- moves `foo()` into `_guard` and drops at the end of the block |
| 50 | + |
| 51 | +Another common source of confusion is the lifetimes of temporaries in `match` statements and the like: |
| 52 | + |
| 53 | +```rust |
| 54 | +match foo.lock().data.copy_out() { |
| 55 | + ... |
| 56 | +} // lock released here! |
| 57 | +``` |
| 58 | + |
| 59 | +`let guard = foo; ...; drop(guard);` has the advantage of explicitness, so does something like `foo.with(|guard| ...)` |
| 60 | + |
| 61 | +### Clarity for unsafe code can be quite important |
| 62 | + |
| 63 | +There are known footguns today with the timing of destructors and unsafe code. For example, `CString::new().as_ptr()` is a common thing people try to do that does not work. Eager destructors would enable more motion, which might exacerbate the problem. |
| 64 | + |
| 65 | +In addition, unsafe code means we might not be able to know the semantics associated with a destructor, such as what precisely a `Mutex<()>` guards, and moving a drop earlier *will* break some unsafe code in hard-to-detect ways. |
| 66 | + |
| 67 | +## Alternatives |
| 68 | + |
| 69 | +- Scoped methods |
| 70 | +- let blocks |
| 71 | +- "Defer" type constructs or scoped guard type constructs from other languages |
| 72 | + - Go |
| 73 | + - D |
| 74 | + - Python |
| 75 | +- Built-in macros or RAII/closure-based helpers in the standard library. |
| 76 | + - Note that the [scopeguard](https://crates.io/crates/scopeguard) crate offers macros like `defer!` that inject a let into the block. |
0 commit comments