Description
When pattern bindings are lowered to MIR, they're dropped in reverse order of declaration1. Normally, for each binding, this means running any necessary drop glue, then dropping its storage. However, with let
-else
, the drop glue is run for all bindings first, then all their storages are dropped. As a result of not interleaving the drops and the StorageDead
s, bindings without meaningful drops live slightly longer than bindings with meaningful drops declared at the same time, regardless of declaration order. Example (playground link):
struct Struct<T>(T);
impl<T> Drop for Struct<T> {
fn drop(&mut self) {}
}
fn main() {
{
// This is an error: `short1` is dead before `long1` is dropped.
let (mut long1, short1) = (Struct(&0), 1);
long1.0 = &short1;
}
{
// This is OK: `short2`'s storage is live until after `long2`'s drop runs.
#[expect(irrefutable_let_patterns)]
let (mut long2, short2) = (Struct(&0), 1) else { unreachable!() };
long2.0 = &short2;
}
{
// Sanity check: `short3`'s drop is significant; it's dropped before `long3`:
let tmp = Box::new(0);
#[expect(irrefutable_let_patterns)]
let (mut long3, short3) = (Struct(&tmp), Box::new(1)) else { unreachable!() };
long3.0 = &short3;
}
}
Implementation-wise, this arises because storage for a let
-else
's bindings is initialized all at once before match lowering (at which point the StorageDead
s are scheduled), but their drops are scheduled afterwards by match lowering. Also of note: the order in which match lowering schedules drops isn't the order in which patterns are normally visited1, so if special treatment is still needed for let
-else
, care needs to be taken to make sure drop order doesn't change.
Zulip stream with details on implementation history: https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/dropck.20inconsistency.20between.20.60let.60.20and.20.60let.60-.60else.60/with/522465186
Related: #142057
cc @rust-lang/lang since fixing this in any direction would change what programs are valid. I imagine that needs a T-lang decision?
cc @dingxiangfei2009, though I'd be happy to try implementing a fix myself once a decision is made on what the proper behavior should be.
@rustbot label: +T-compiler +T-lang +A-MIR
Footnotes
-
Match lowering treats bindings in or-patterns as occurring after bindings outside of or-patterns and differs between
let
andmatch
(example). Additionally, it treats bindings in avar @ subpattern
'ssubpattern
as occurring before thevar
(Unnecessary 'cannot bind by-move with sub-bindings' withbindings_after_at
#69971). ↩ ↩2