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
This exercise compares two pieces of Rust syntax that both reuse a variable name: shadowing and assignment. They look similar but do different things to the value the name held before. You will see the difference in a log that records when each value is created and when it is dropped (destroyed).
pub fn shadow_timeline(scene: i64) -> Vec<String>
scene selects one of five small scenes, numbered 0 to 4, and the function
returns that scene's log lines in the order they were recorded. Any other
scene value returns an empty vector; the provided match already does this.
Two ways to reuse a name
let t = make("a");
let t = make("b"); // shadowing: a second `let` creates a NEW binding with the same name
let mut u = make("c");
u = make("d"); // assignment: the SAME binding receives a new value
A binding is the variable that a let introduces. Shadowing means
declaring a new binding whose name hides an earlier one in the same or an
enclosing scope.
Shadowing does not destroy "a". The first binding still exists and still
owns its value; the name t simply refers to the second binding from that
point on. Both values are dropped when the scope ends, and local variables
in one scope are dropped in the reverse of their declaration order, so the
log for the code above (with a marker line at the end of the body) is:
new a, new b, end of body, drop b, drop a
Assignment is different. The slot u already holds "c", so before "d"
can be stored there, Rust drops the old value. This happens at the
assignment, not at the end of the scope.
Check your understanding
In u = make("d"), is "c" dropped before or after "d" is built?
Show answer
Hide answer
After. The Rust Reference specifies this order for assignment:
- evaluate the right-hand side, producing
"d" - drop the value currently in
u, which is"c" - move
"d"intou
So the log reads new d, then drop c. The order matters when the value
stands for a resource. If u held a lock guard or an open file, the new
resource is acquired before the old one is released.
One consequence is that the right-hand side can fail without leaving u
empty. If building "d" panics, u still holds "c", and "c" is dropped
once, during unwinding, like any other local.
Two special cases: assigning to a local that has not been initialized yet
(let u; u = make("x");), or to one whose value has been moved out, drops
nothing at that point, because there is no value in the slot.
E0384: assignment needs mut
Shadowing needs no mut, because each let creates a fresh binding.
Assigning to an existing binding requires that binding to be declared
let mut. The starter's scene 1 omits mut, and rustc rejects it:
error[E0384]: cannot assign twice to immutable variable `u`
|
| let u = tracked("c".to_string(), log.clone());
| - first assignment to `u`
| note(log.clone(), format!("holding {}", u.name));
| u = tracked("d".to_string(), log.clone());
| ^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
| let mut u = tracked("c".to_string(), log.clone());
| +++
Fix that error, then write scenes 2, 3 and 4.
The provided helpers
The grader adds these definitions to your program:
type Log = Rc<RefCell<Vec<String>>>; // a shared, growable list of lines
struct Tracked { name: String, log: Log }
fn tracked(name: String, log: Log) -> Tracked // pushes "new <name>"
impl Drop for Tracked // drop pushes "drop <name>"
fn note(log: Log, line: String) // pushes `line` unchanged (a "marker")
fn new_log() -> Log
fn snapshot(log: Log) -> Vec<String> // copies the lines out
log.clone() copies the Rc handle, not the list, so every clone appends to
the same log. You can read a Tracked value's name field, as in u.name.
The five scenes
Every line below is recorded through tracked, note or a drop. Names such
as g{i} mean the loop counter formatted into the string, for example
format!("g{i}").
0: plain shadowing (provided). Two locals both named _t, holding a
and then b, followed by the marker "end of body".
1: assignment. One mut local u holding c. Emit
format!("holding {}", u.name). Assign a new Tracked named d to u. Emit
format!("holding {}", u.name) again.
2: shadowing in an inner block. A 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 a new Tracked
named z{i} to u. After the loop, emit format!("final {}", u.name).
Scene 2 shows that shadowing is scoped: after the inner block's closing
brace, the name t refers to the outer binding again, and the outer value
was never dropped or changed. Scene 3 shows that a loop body is a scope too,
so both of that iteration's values are dropped, h{i} first, at the end of
every iteration.
Scene 0 names its locals _t rather than t because they are never read,
and the leading underscore silences the unused-variable warning. Do not
replace them with let _ = ...: the _ pattern binds nothing, so the value
is dropped immediately at the end of that statement instead of at the end of
the scope.
Clippy and shadowing
Grading compiles your code, runs the tests and runs cargo clippy with
-D warnings, which turns every warning into an error.
Clippy's redundant_locals lint, in the default-enabled suspicious group,
flags a rebinding such as let x = x; that changes nothing. In the Clippy
shipped with Rust 1.98.1, the lint did not fire when the rebinding changed
mutability (let mut x = ...; let x = x;) or appeared inside a closure body
to make the closure capture x by value, which are the common deliberate
uses.
The lints that flag shadowing itself, shadow_same, shadow_reuse and
shadow_unrelated, belong to the restriction group, which is off by
default. The shadowing in this exercise therefore passes the default Clippy
configuration.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.