We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership I: Moves, Copy, Clone, Drop step 17 of 20
Shadowing is not moving
Five small scenes about two operations that look identical and are not.
pub fn shadow_timeline(scene: i64) -> Vec<String>
Two things that both reuse a name
let t = make("a");
let t = make("b"); // shadowing: a NEW binding that happens to reuse the name
let mut u = make("c");
u = make("d"); // assignment: the SAME binding, given a new value
Learners routinely believe the first one destroys "a". It does not. Shadowing
creates a second binding; the first one still exists, still owns its value, and
is simply no longer reachable by that name. Both die at the end of the scope, in
reverse declaration order, exactly like any two locals:
new a, new b, end of body, drop b, drop a
Assignment is the operation that destroys. Writing into a slot that already
holds a value must first get rid of what is there, or the old value’s
destructor would never run and its memory would leak. So u = make("d") drops
"c" — and it does it at the assignment point, not at the end of the scope.
::: question In u = make("d"), does "c" drop before or after "d" is built?
After. The order is:
-
evaluate the right-hand side, producing
"d" -
drop the old occupant of the slot,
"c" -
move
"d"into the slot
so the log reads new d, then drop c. Predicting this backwards is one of the
most common small errors people make when reasoning about a Drop type, and it
matters in real code: if the value is a lock guard or a file handle, “the new
one is acquired before the old one is released” is a very different program from
“the old one is released first”.
The reason for the order is straightforward once you ask what happens if step 1 panics. If the old value had already been dropped, the slot would hold garbage and the unwinder would have to know not to drop it again. Building first keeps the slot valid at every instant. :::
E0384: assignment needs mut
Shadowing needs no mut, because each let is a fresh binding. Assignment does:
error[E0384]: cannot assign twice to immutable variable `u`
|
| let u = tracked("c", log.clone());
| - first assignment to `u`
| u = tracked("d", log.clone());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable
help: consider making this binding mutable: `mut u`
The starter contains that error. Fix it, then build the remaining scenes.
The scaffolding
Injected by the grader:
fn tracked(name: String, log: Log) -> Tracked // pushes "new <name>"
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>
Tracked has a name: String field you can read.
The five scenes
Scene 0 is written for you.
0 — plain shadowing. Two locals both called _t, holding a then b; then
the marker "end of body".
1 — assignment. One mut local u, holding c. Emit
format!("holding {}", u.name). Assign d into u. Emit
format!("holding {}", u.name) again.
2 — shadowing in an inner block. Local t holding e; emit
format!("outer {}", t.name); then a block containing a shadowing local t
holding f and the marker format!("inner {}", t.name); after the block, emit
format!("back to {}", t.name).
3 — shadowing in a loop. For i in 0..2: a local t holding g{i}, the
marker format!("first {}", t.name), a shadowing local t holding h{i}, and
the marker format!("iter {}", t.name).
4 — assignment in a loop. A mut local u holding z0. For i in 1..3:
emit format!("holding {}", u.name), then assign z{i} into u. After the
loop, emit format!("final {}", u.name).
Scene 2 is the one that shows shadowing is scoped: after the inner brace, the
name t refers to the outer value again, and the outer value was never touched.
Scene 3 shows that a loop body is a scope like any other — both shadows die every
iteration.
One lint to know about
There is a tempting sixth scene — the “re-bind by move” idiom let x = x;,
which is genuinely useful for forcing a move into a closure or narrowing
mutability. It is not here, because clippy’s redundant_locals fires on it:
error: redundant redefinition of a binding `x`
redundant_locals is a suspicious-level lint, on by default at warn, which
-D warnings promotes to an error. So the idiom would fail this problem’s gate
through no fault of yours. Worth knowing it exists, and worth knowing why you
cannot practise it here.
The shadow_same, shadow_reuse and shadow_unrelated lints, by contrast, are
all in the restriction group and off by default. Shadowing is idiomatic
Rust; nothing in the default configuration discourages it.
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.