We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Smart Pointers and Interior Mutability step 13 of 26
`let _ =` versus `let _x =`: the guard that dies instantly
Four blocks, four ways of binding the same guard. Produce the exact log.
pub fn compare_bindings() -> Vec<String>
pub fn one_form(which: usize) -> Vec<String>
A Span logs enter <name> when built and exit <name> when dropped —
the guard from the previous item, already written for you. mark(log, text)
appends a plain line.
The four blocks, exactly:
fn block_a(log: &Log) { fn block_b(log: &Log) {
mark(log, "A: before"); mark(log, "B: before");
let _ = Span::new("a", log); let _s = Span::new("b", log);
mark(log, "A: after"); mark(log, "B: after");
} }
fn block_c(log: &Log) { fn block_d(log: &Log) {
mark(log, "C: before"); mark(log, "D: before");
Span::new("c", log); let _s2 = Span::new("d", log);
mark(log, "C: after"); mark(log, "D: middle");
} drop(_s2);
mark(log, "D: after");
}
compare_bindings() runs all four against one log and returns it.
one_form(which) runs just block 0/1/2/3 against a fresh log —
anything else returns an empty log.
Predict the answer before you run it. That is the exercise.
The whole item in one sentence
_is a pattern, not an identifier.
let _ = expr; does not create a binding. It matches the value against a
wildcard, which binds nothing, so the value is a temporary and a temporary
is dropped at the end of its statement. The guard’s destructor has already run
by the time the next line executes.
let _s = expr; does create a binding. The leading underscore is nothing
but a convention that silences the unused_variables warning — _s is an
ordinary variable name, the value is owned by it, and it is dropped at the end
of the enclosing block like any other local.
Those two lines are three characters apart and produce two completely different programs.
The consequence, in production
let _ = mutex.lock();
// "critical section"
shared.do_something_dangerous();
The lock is acquired and released on the first line. Everything after it runs unprotected, and it will pass every test you write on an unloaded machine, because nothing is contending. It fails under load, in production, rarely, and the diff that introduced it looks completely innocent.
Clippy takes this seriously enough to make it one of the very few
deny-by-default lints: let_underscore_lock is in the correctness group,
which means “this is a bug”, not “this is unidiomatic”.
Be equally clear about what is not caught. rustc’s let_underscore_drop
fires on let _ = <a type with a destructor> in general — and it is
allow-by-default, so the gate will not catch it. If your guard is a
transaction, a span, a tracing scope, a temporary file, or anything else that
is not literally a lock, let _ = guard; compiles clean and silently does
nothing. Knowing which of your footguns the tooling actually covers is part of
the job.
let _ versus _x versus nothing at all
| form | binds? | dropped |
|---|---|---|
let _ = f(); |
no | end of statement |
f(); |
no | end of statement |
let _x = f(); |
yes | end of block |
let x = f(); |
yes | end of block (plus an unused warning if never read) |
let _x = f(); drop(_x); |
yes |
at the drop call |
Rows one and two are the same program. Row three is the one you almost always
want when you write a guard. Row five is how you end a critical section early
without introducing a block — and it works precisely because _x is a
name, which is exactly what _ is not.
The starter proves that last point by failing to compile:
error: in expressions, `_` can only be used on the left-hand side of an assignment
| drop(_);
| ^ `_` not allowed here
You cannot pass _ to drop, because there is nothing there to pass. It was
never a variable.
Preferring a block
When a critical section has a natural extent, an explicit block says so better
than a drop call does:
{
let _guard = lock();
// exactly this much is protected
}
The scope is the documentation, it survives refactoring, and it still does the right thing if the body returns early or panics.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.