src/doc/rustc-dev-guide/src/mir/drop-elaboration.md MARKDOWN 194 lines View on github.com → Search inside
1# Drop elaboration23## Dynamic drops45According to the [reference][reference-drop]:67> When an initialized variable or temporary goes out of scope, its destructor8> is run, or it is dropped. Assignment also runs the destructor of its9> left-hand operand, if it's initialized. If a variable has been partially10> initialized, only its initialized fields are dropped.1112When building the MIR, the `Drop` and `DropAndReplace` terminators represent13places where drops may occur. However, in this phase, the presence of these14terminators does not guarantee that a destructor will run. That's because the15target of a drop may be uninitialized (usually because it has been moved from)16before the terminator is reached. In general, we cannot know at compile-time whether a17variable is initialized.1819```rust20let mut y = vec![];2122{23    let x = vec![1, 2, 3];24    if std::process::id() % 2 == 0 {25        y = x; // conditionally move `x` into `y`26    }27} // `x` goes out of scope here. Should it be dropped?28```2930In these cases, we need to keep track of whether a variable is initialized31*dynamically*. The rules are laid out in detail in [RFC 320: Non-zeroing32dynamic drops][RFC 320].3334## Drop obligations3536From the RFC:3738> When a local variable becomes initialized, it establishes a set of "drop39> obligations": a set of structural paths (e.g. a local `a`, or a path to a40> field `b.f.y`) that need to be dropped.41>42> The drop obligations for a local variable x of struct-type `T` are computed43> from analyzing the structure of `T`. If `T` itself implements `Drop`, then `x` is a44> drop obligation. If `T` does not implement `Drop`, then the set of drop45> obligations is the union of the drop obligations of the fields of `T`.4647When a structural path is moved from (and thus becomes uninitialized), any drop48obligations for that path or its descendants (`path.f`, `path.f.g.h`, etc.) are49released. Types with `Drop` implementations do not permit moves from individual50fields, so there is no need to track initializedness through them.5152When a local variable goes out of scope (`Drop`), or when a structural path is53overwritten via assignment (`DropAndReplace`), we check for any drop54obligations for that variable or path.  Unless that obligation has been55released by this point, its associated `Drop` implementation will be called.56For `enum` types, only fields corresponding to the "active" variant need to be57dropped. When processing drop obligations for such types, we first check the58discriminant to determine the active variant. All drop obligations for variants59besides the active one are ignored.6061Here are a few interesting types to help illustrate these rules:6263```rust64struct NoDrop(u8); // No `Drop` impl. No fields with `Drop` impls.6566struct NeedsDrop(Vec<u8>); // No `Drop` impl but has fields with `Drop` impls.6768struct ThinVec(*const u8); // Custom `Drop` impl. Individual fields cannot be moved from.6970impl Drop for ThinVec {71    fn drop(&mut self) { /* ... */ }72}7374enum MaybeDrop {75    Yes(NeedsDrop),76    No(NoDrop),77}78```7980## Drop elaboration8182One valid model for these rules is to keep a boolean flag (a "drop flag") for83every structural path that is used at any point in the function. This flag is84set when its path is initialized and is cleared when the path is moved from.85When a `Drop` occurs, we check the flags for every obligation associated with86the target of the `Drop` and call the associated `Drop` impl for those that are87still applicable.8889This processtransforming the newly built MIR with its imprecise `Drop` and90`DropAndReplace` terminators into one with drop flagsis known as drop91elaboration. When a MIR statement causes a variable to become initialized (or92uninitialized), drop elaboration inserts code that sets (or clears) the drop93flag for that variable. It wraps `Drop` terminators in conditionals that check94the newly inserted drop flags.9596Drop elaboration also splits `DropAndReplace` terminators into a `Drop` of the97target and a write of the newly dropped place. This is somewhat unrelated to what98we've discussed above.99100Once this is complete, `Drop` terminators in the MIR correspond to a call to101the "drop glue" or "drop shim" for the type of the dropped place. The drop102glue for a type calls the `Drop` impl for that type (if one exists), and then103recursively calls the drop glue for all fields of that type.104105## Drop elaboration in `rustc`106107The approach described above is more expensive than necessary. One can imagine108a few optimizations:109110- Only paths that are the target of a `Drop` (or have the target as a prefix)111  need drop flags.112- Some variables are known to be initialized (or uninitialized) when they are113  dropped. These do not need drop flags.114- If a set of paths are only dropped or moved from via a shared prefix, those115  paths can share a single drop flag.116117A subset of these are implemented in `rustc`.118119In the compiler, drop elaboration is split across several modules. The pass120itself is defined [here][drops-transform], but the [main logic][drops] is121defined elsewhere since it is also used to build [drop shims][drops-shim].122123Drop elaboration designates each `Drop` in the newly built MIR as one of four124kinds:125126- `Static`, the target is always initialized.127- `Dead`, the target is always **un**initialized.128- `Conditional`, the target is either wholly initialized or wholly129  uninitialized. It is not partly initialized.130- `Open`, the target may be partly initialized.131132For this, it uses a pair of dataflow analyses, `MaybeInitializedPlaces` and133`MaybeUninitializedPlaces`. If a place is in one but not the other, then the134initializedness of the target is known at compile-time (`Dead` or `Static`).135In this case, drop elaboration does not add a flag for the target. It simply136removes (`Dead`) or preserves (`Static`) the `Drop` terminator.137138For `Conditional` drops, we know that the initializedness of the variable as a139whole is the same as the initializedness of its fields. Therefore, once we140generate a drop flag for the target of that drop, it's safe to call the drop141glue for that target.142143### `Open` drops144145`Open` drops are the most complex, since we need to break down a single `Drop`146terminator into several different ones, one for each field of the target whose147type has drop glue (`Ty::needs_drop`). We cannot call the drop glue for the148target itself because that requires all fields of the target to be initialized.149Remember, variables whose type has a custom `Drop` impl do not allow `Open`150drops because their fields cannot be moved from.151152This is accomplished by recursively categorizing each field as `Dead`,153`Static`, `Conditional` or `Open`. Fields whose type does not have drop glue154are automatically `Dead` and need not be considered during the recursion. When155we reach a field whose kind is not `Open`, we handle it as we did above. If the156field is also `Open`, the recursion continues.157158It's worth noting how we handle `Open` drops of enums. Inside drop elaboration,159each variant of the enum is treated like a field, with the invariant that only160one of those "variant fields" can be initialized at any given time. In the161general case, we do not know which variant is the active one, so we will have162to call the drop glue for the enum (which checks the discriminant) or check the163discriminant ourselves as part of an elaborated `Open` drop. However, in164certain cases (within a `match` arm, for example) we do know which variant of165an enum is active. This information is encoded in the `MaybeInitializedPlaces`166and `MaybeUninitializedPlaces` dataflow analyses by marking all places167corresponding to inactive variants as uninitialized.168169### Cleanup paths170171TODO: Discuss drop elaboration and unwinding.172173## Aside: drop elaboration and const-eval174175In Rust, functions that are eligible for evaluation at compile-time must be176marked explicitly using the `const` keyword. This includes implementations  of177the `Drop` trait, which may or may not be `const`. Code that is eligible for178compile-time evaluation may only call `const` functions, so any calls to179non-const `Drop` implementations in such code must be forbidden.180181A call to a `Drop` impl is encoded as a `Drop` terminator in the MIR. However,182as we discussed above, a `Drop` terminator in newly built MIR does not183necessarily result in a call to `Drop::drop`. The drop target may be184uninitialized at that point. This means that checking for non-const `Drop`s on185the newly built MIR can result in spurious errors. Instead, we wait until after186drop elaboration runs, which eliminates `Dead` drops (ones where the target is187known to be uninitialized) to run these checks.188189[RFC 320]: https://rust-lang.github.io/rfcs/0320-nonzeroing-dynamic-drop.html190[reference-drop]: https://doc.rust-lang.org/reference/destructors.html191[drops]: https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc_mir_transform/src/elaborate_drops.rs192[drops-shim]: https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc_mir_transform/src/shim.rs193[drops-transform]: https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc_mir_transform/src/elaborate_drops.rs

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.