Skip to content

Commit 3b4226a

Browse files
authored
Merge pull request #103 from nikomatsakis/eager-drop-design-notes
add design notes from lang-team#86 (eager drop)
2 parents 494a53b + f434d73 commit 3b4226a

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@
2727
- [Generalizing coroutines](./design_notes/general_coroutines.md)
2828
- [Extending the capabilities of compiler-generated function types](./design_notes/fn_type_trait_impls.md)
2929
- [Auto traits](./design_notes/auto_traits.md)
30+
- [Eager drop](./design_notes/eager_drop.md)

src/design_notes/eager_drop.md

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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

Comments
 (0)