Skip to content

drop-checking is more permissive when let statements have an else block #142056

Open
@dianne

Description

@dianne

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 StorageDeads, 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 StorageDeads 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

  1. Match lowering treats bindings in or-patterns as occurring after bindings outside of or-patterns and differs between let and match (example). Additionally, it treats bindings in a var @ subpattern's subpattern as occurring before the var (Unnecessary 'cannot bind by-move with sub-bindings' with bindings_after_at #69971). 2

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-MIRArea: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.htmlC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions