while let $pat = $expr
$block
is the same as
loop {
if let $pat = $expr
$block
else {
break;
}
}
So this is not really about while let and loop being different; it is about let $pat = $expr and if let $pat = $expr (or equivalently a match binding) being different / having different semantics.
So let's go back to a more minimal example, getting rid of loops since they don't play any role:
match vs. let
First of all, let's set up the stuff that replicates a mutex guard behavior:
-
mutex.lock()constructs avaluewith drop glue, i.e., thatmem::needs_drop():#[derive(Default)] // default() <=> mutex.lock() struct WithDropGlue; impl Drop for WithDropGlue { fn drop (&mut self) {} } -
value.recv()borrows thisvalueto, in turn, create an "owned thing", i.e., something that is: 'static(hencevaluecould be dropped while this thing exists without causing any issue whatsoever):impl HasDropGlue { fn recv (self: &'_ Self) -> () {} }
match case
fn foo ()
{
// |-- mutex.lock() ----|
match HasDropGlue::default().recv() { _smth => {
match_body();
}}
}
gives the following MIR (with panic = abort to remove unwind paths):
⇒ The temporary is indeed only dropped at the end of the whole
matchexpression
let
fn foo ()
{
{
// |-- mutex.lock() ----|
let _smth = HasDropGlue::default().recv();
body();
}
}
⇒ The temporary is dropped right after the
letassignment
Summary
fn foo ()
{
// |-- mutex.lock() ----|
match HasDropGlue::default().recv() { _smth => {
match_body();
}} // drop::<HasDropGlue>(__guard__)
}
fn foo ()
{
{
// |-- mutex.lock() ----|
let _smth = HasDropGlue::default().recv();
// drop::<HasDropGlue>(__guard__)
body();
}
}
what if revc() had returned something tied to the '_ input lifetime ?
Then the the match version would still work, with a temporary held alive as long as needed; whereas the classic let version would fail to compile with a "temporary does not live long enough" error.
- Note that when raw pointers are involved, such as with
CString::new().as_ptr(), Rust does not see the borrow and can thus lead to a use-after-free unsoundness.
What to make of all this
This is one of the "quirks" of Rust, which proves that if explicit RAII semantics are wanted (such as with lock guards), then anonymous temporaries should be avoided. You may submit an RFC to change this, to get a change of temporaries drop placement, but know that:
-
this would require, at least, a new edition
-
Having program semantics change with an edition change would be very weird (why should a line in
Cargo.tomlchange the semantics of a program without a compilation error? c.f.CString::new().as_ptr())
Thus this behavior cannot be changed.
On the other hand, I can see the issue of this being a particularly obscure thing for Rust, which goes against its "explicit semantics" philosphy: I thus think that filing an RFC for a warning lint against temporaries with drop glue in match would be a great thing to do, and if it fails, the lint could be added to clippy instead.
- Actually, it would be great if, in a similar fashion to
#[must_use]types, we could have#[time_sensitive_drop]or#[must_drop_explicitely]types.

