We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership I: Moves, Copy, Clone, Drop step 13 of 20
Drop order is specified
Build eight specified arrangements of values and let their destructors write the answer.
pub fn order_report(scenario: i64) -> Vec<String>
Why this is worth memorising
Deterministic destruction is Rust’s headline advantage over a garbage-collected language: you know when a file closes, a lock releases, a connection returns to the pool. But that is only an advantage if the order is knowable, and every RAII pattern you will ever write depends on these rules.
The rules, all of them:
| Arrangement | Order |
|---|---|
let locals in a scope |
reverse declaration order |
| struct fields | declaration order |
| tuple elements | declaration order |
array / Vec / slice elements |
front to back |
a type with its own Drop impl |
its drop runs first, then its fields |
let _ = expr; |
drops immediately, at the end of that statement |
let _x = expr; |
a real binding — lives to the end of the scope |
| shadowed bindings | both live to the end of the scope, then drop in reverse |
Locals reverse and fields forward is genuinely non-obvious, and it is not arbitrary. Locals reverse because later declarations may depend on earlier ones — a guard declared after the thing it guards must be released before it. Fields go forward because a struct’s fields are constructed in declaration order and there is no dependency to invert.
The one that catches everyone is the last two rows of the middle group:
let _ = f(); and let _x = f(); look like the same statement and have
completely different lifetimes. _ is not an identifier; it is a pattern that
matches and discards. Nothing owns the value, so it dies at the semicolon.
::: question A Vec<T> drops its elements front to back, but let locals drop in reverse. Isn’t that inconsistent?
It looks inconsistent and it isn’t, because the two orders answer different
questions.
Locals are a stack. You wrote them in an order, later ones may have been built out of earlier ones, and unwinding a stack means popping it — last in, first out. Reversing is the only order that never destroys something another local might still be holding a reference to.
A Vec‘s elements are peers. There is no “the third element was built out
of the second”, so there is no dependency to respect, and front-to-back is
simply the cheapest traversal: walk the buffer forward, drop each element, free
the buffer once. Same for arrays and slices.
Struct fields sit in the peer category too, which is why they go forward. :::
The scaffolding
The grader injects these. Construction is silent — only destructors and your own markers appear in the log.
fn tracked(name: String, log: Log) -> Tracked // silent
impl Drop for Tracked // pushes "drop <name>"
fn note(log: Log, line: String) // pushes an arbitrary line
fn new_log() -> Log
fn snapshot(log: Log) -> Vec<String>
struct Trio {
first: Tracked,
second: Tracked,
third: Tracked,
log: Log,
}
fn trio(a: String, b: String, c: String, log: Log) -> Trio
impl Drop for Trio // pushes "drop trio"
Log is a cheap handle; clone it freely with log.clone().
The eight scenarios
order_report(n) runs scene n and returns the log. Scene 0 is written for
you as a worked example; build the rest.
0 — three locals. Locals named a, b, c, in that order. Nothing else.
1 — a struct. One local: trio("p", "q", "r", log).
2 — containers. In this order: a tuple of x and y; an array of p and
q; a Vec of u and v. Build the Vec with Vec::from([...]) — clippy
rejects a vec![] whose value is never used as one.
3 — an inner block. Local a; then a block containing local b and the
marker "inside"; then the marker "after"; then local c.
4 — binding or not. Marker "start"; let _ = a value named imm;
marker "mid"; let _held = a value named held; marker "end".
5 — shadowing. Two locals, both named _s, holding values named v and
then w; then the marker "done".
6 and 7 — leaving early. Local a; then a block containing local b, the
marker "inner", and — only in scene 6 — a return. If the function does not
return there, it emits the marker "stayed", leaves the block, and emits
"after".
Scene 6 is the interesting one: an early return from inside a nested block
still destroys the inner block’s locals first and the outer ones second. Rust
unwinds inside-out, and it does the same on a panic, which is what makes RAII
guards sound in the presence of both.
Two things you cannot observe here
Closure captures. Do not build a test around the order in which a closure destroys its captured values. The Rust Reference deliberately leaves it unspecified, and code that depends on it is code that may break.
Temporaries. The scenes above avoid bare temporaries on purpose. The rules
for when an unnamed intermediate value dies are subtle, and edition 2024
changed several of them (tail expressions and if let scrutinees in
particular). They are worth learning — later, deliberately, and not by accident
in the middle of a drop-order exercise.
Two clippy lints in this area are worth knowing by name even though both are
allow-by-default: significant_drop_tightening (a lock guard held longer than
it needs to be) and significant_drop_in_scrutinee (a guard kept alive for the
whole of a match because it appeared in the scrutinee). Both are real bugs in
real code, and both are drop-order bugs.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.