|
| 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 `let _guard = foo` pattern can be easily confused with `let _ = foo`. `let guard = foo; ...; drop(guard);` has the advantage of explicitness, so does something like `foo.with(|guard| ...)` |
| 45 | + |
| 46 | +Temporary rules interact with `match` in ways that surprise people: |
| 47 | + |
| 48 | +```rust= |
| 49 | +match foo.lock().data.copy_out() { |
| 50 | + ... |
| 51 | +} // lock released here! |
| 52 | +``` |
| 53 | + |
| 54 | +Temporary rules interact poorly with unsafe code today, e.g. `CString::new().as_ptr()` is a known footgun. Would this change help? Or perhaps just change the footguns around. (The change as _written_ would not help here, because the temporary would be dropped probably _even earlier_.) |
| 55 | + |
| 56 | +## Alternatives |
| 57 | + |
| 58 | +- Scoped methods |
| 59 | +- let blocks |
| 60 | +- "Defer" type constructs or scoped guard type constructs from other languages |
| 61 | + - Go |
| 62 | + - D |
| 63 | + - Python |
0 commit comments