Drop and Resource Management
Variable Scope and Destruction
A variable's drop scope is the lexical region at whose exit the compiler runs the variable's destructor. Understanding how nested blocks, temporary expressions, let _ = ... wildcards, and temporary lifetime extension affect destruction timing is essential when managing locks and scoped guards.
Lexical Block Scopes and Shadowing
- Nested blocks (
{ ... }): A variable declared inside an inner{ ... }block is dropped immediately at the closing brace}of that block. - Variable shadowing (
let x = ...; let x = ...;): Re-declaring a variable with the same name (let x = ...) does not immediately drop the original binding! Both bindings remain alive in the current scope and are dropped in reverse declaration order when the enclosing block ends. - Reassignment (
x = new_val;): Assigning a new value to an existing mutable binding (mut x) does immediately drop the old value held inxbefore storingnew_val.
struct Token(&'static str);
impl Drop for Token {
fn drop(&mut self) {
println!("dropped {}", self.0);
}
}
fn main() {
println!("--- Shadowing keeps old binding alive until block end ---");
{
let x = Token("x1 (shadowed)");
let x = Token("x2 (shadowing)");
println!("active x is {}", x.0);
}
println!("--- Reassignment drops old value immediately ---");
{
let mut y = Token("y1 (initial)");
println!("before reassignment");
y = Token("y2 (replacement)");
println!("after reassignment, active y is {}", y.0);
}
}
--- Shadowing keeps old binding alive until block end ---
active x is x2 (shadowing)
dropped x2 (shadowing)
dropped x1 (shadowed)
--- Reassignment drops old value immediately ---
before reassignment
dropped y1 (initial)
after reassignment, active y is y2 (replacement)
dropped y2 (replacement)
The Wildcard Trap: let _ = expr vs. let _guard = expr
The wildcard pattern _ does not bind a value to a variable; let _ = expr; evaluates expr and drops the result immediately at the semicolon. In contrast, a named variable prefixed with an underscore (let _guard = expr;) binds the value to a real local variable that lives until the end of the enclosing block.
let _ = acquire_guard(); // Dropped immediately at `;` on this line!
perform_critical_work(); // Runs UNPROTECTED!
let _guard = acquire_guard(); // Bound to `_guard`; stays alive until `}`!
perform_critical_work(); // Runs while `_guard` is active!
struct ScopeTimer(&'static str);
impl Drop for ScopeTimer {
fn drop(&mut self) {
println!("timer {:?} finished", self.0);
}
}
fn main() {
let _ = ScopeTimer("wildcard (drops immediately)");
let _named = ScopeTimer("named _named (drops at end of main)");
println!("executing body of main...");
}
timer "wildcard (drops immediately)" finished
executing body of main...
timer "named _named (drops at end of main)" finished
Temporary Scopes and Temporary Lifetime Extension
- Temporary values: An unnamed intermediate value created inside a statement (such as
Mutex::new(0).lock().unwrap().to_string();) lives until the end of the enclosing statement (the terminating;), unless it is inside the scrutinee of anif letormatchexpression where it can remain alive for the matched arm. - Temporary lifetime extension: When a
letstatement borrows directly from a temporary R-value expression (let r = &vec![1, 2, 3];), the compiler extends the lifetime of the temporaryVec<i32>to match the enclosing block scope ofr.