Skip to content

← Smart Pointers and Interior Mutability step 13 of 26

Medium Primitives

Keep a guard alive with a named binding

pub fn compare_bindings() -> Vec<String>
pub fn one_form(which: usize) -> Vec<String>

Repair four blocks to demonstrate when a newly created guard is dropped. Use the supplied Span and logging helpers rather than hard-coding the log. Span::new(name, log) records enter <name>; its destructor records exit <name>. mark(log, text) appends an ordinary entry.

Each block starts with its before marker and constructs its named span. Use these forms and produce the following entries in order:

Block Form Entries
A let _ = Span::new("a", log); A: before, enter a, exit a, A: after
B let _s = Span::new("b", log); B: before, enter b, B: after, exit b
C Span::new("c", log); C: before, enter c, exit c, C: after
D Bind Span::new("d", log) to a name, then explicitly drop it. D: before, enter d, D: middle, exit d, D: after

compare_bindings() runs A, B, C and D against one fresh log and returns their concatenated entries. one_form(which) uses a separate fresh log: 0, 1, 2 and 3 select A, B, C and D; any other index returns [].

A wildcard does not bind a value

In block A, _ is a wildcard pattern. The newly constructed Span is not stored in a local binding, so it is dropped at the end of that statement, before the after marker. The bare expression statement in C has the same drop timing for this Span.

In B, _s is an ordinary variable name. Its leading underscore suppresses the unused-variable warning; it does not shorten the value's lifetime. The span remains owned by _s until the block ends, after the final marker. D uses the same kind of binding but passes it to drop between its middle and after markers.

The starter's drop(_) cannot compile: _ is not an expression naming a value. Give the guard a name so drop(name) can consume it.

Newly created values and existing locals differ

Do not generalize A's behavior to let _ = existing_guard;. A wildcard does not move an existing local into a new binding or explicitly destroy it. For example, this existing span remains alive until the block ends:

let guard = Span::new("kept", log);
let _ = guard;
mark(log, "still inside");
// `guard` is dropped when its owning scope ends.

That produces enter kept, still inside, then exit kept. To end an existing guard's lifetime here, use drop(guard) instead. See the wildcard pattern rules.

Applying this to locks

When a freshly returned lock guard is discarded, the lock does not remain held for later statements. Bind a successfully acquired guard for the scope that needs it:

let mutex = std::sync::Mutex::new(0);
{
    let mut guard = mutex.lock().expect("lock poisoned");
    *guard += 1;
} // The guard is dropped and the lock released here.

rustc's deny-by-default let_underscore_lock catches discarding recognized lock guards through a wildcard binding. The more general let_underscore_drop lint is allow-by-default. Neither means every custom guard misuse is caught. The Span in this exercise deliberately permits A and C so you can observe their behavior.

Scope-based cleanup also runs on ordinary early returns and panic unwinding, but not on every form of termination, such as process abort. For this exercise, focus on the normal execution order shown in the table. Tests can compare the logs; identical output alone does not prove which binding syntax was used.