Skip to content

Desuraring of destructuring assignment has subtle effects on drop-order / scopes of temporaries #96579

Open
@steffahn

Description

@steffahn

For example

fn main() {
    struct L;
    impl Drop for L {
        fn drop(&mut self) {
            println!("dropped L")
        }
    }
    struct M;
    impl Drop for M {
        fn drop(&mut self) {
            println!("dropped M")
        }
    }

    let x;
    (x = (drop(&L), ()), M);

    println!("---"); // -------------

    let (x1, x2);
    ((x1, x2) = (drop(&L), ()), M);
}

Output:

dropped M
dropped L
---
dropped L
dropped M

I came across this by reading the reference:

Assignee expressions are then desugared to pattern matches followed by sequential assignment.

The desugaring method is straightforward, and is illustrated best by example.

(a, b) = (3, 4);

[a, b] = [3, 4];

Struct { x: a, y: b } = Struct { x: 3, y: 4};

// desugars to:

{
    let (_a, _b) = (3, 4);
    a = _a;
    b = _b;
}

{
    let [_a, _b] = [3, 4];
    a = _a;
    b = _b;
}

{
    let Struct { x: _a, y: _b } = Struct { x: 3, y: 4};
    a = _a;
    b = _b;
}

The difference between

STATEMENT CONTAINING … x = EXPR …

and

STATEMENT CONTAINING … { let _x = EXPR; x = _x } …

is that in the former case, temporaries in EXPR are dropped after the surrounding statement is evaluated, while the latter case has them dropped right after the let _x = EXPR.


It seems to me that a better desugaring would have been to use something like

(a, b) = (3, 4);

[a, b] = [3, 4];

Struct { x: a, y: b } = Struct { x: 3, y: 4};

// desugars to:

match (3, 4) {
    (_a, _b) => {
        a = _a;
        b = _b;
    }
}

match [3, 4] {
    [_a, _b] =>
        a = _a;
        b = _b;
    }
}

match Struct { x: 3, y: 4} {
    Struct { x: _a, y: _b } => {
        a = _a;
        b = _b;
    }
}

Or applied to the example above:

fn main() {
    ………
    
    let (x1, x2);
    // current desugaring
    (
        {
            let (_x1, _x2) = (drop(&L), ());
            x1 = _x1;
            x2 = _x2;
        },
        M,
    );

    println!("---"); // -------------

    let (x1, x2);
    // better desugaring
    (
        match (drop(&L), ()) {
            (_x1, _x2) => {
                x1 = _x1;
                x2 = _x2;
            }
        },
        M,
    );
}
dropped L
dropped M
---
dropped M
dropped L

Full example in the playground.

As a point of comparison, e.g. desugaring of for loops, and similarly many macros, use match in a similar way to ensure proper temporary scopes.

Changing it now might technically be a breaking change, OTOH, the feature of destructuring assignment is still very young, and the change is small. I’m not 100% certain about the "bug" label, but I’ll add it for now, next to "enhancement", to let the reader choose whichever they feel more appropriate: @rustbot label C-bug, C-enhancement, F-destructuring_assignment, T-compiler

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.C-enhancementCategory: An issue proposing an enhancement or a PR with one.F-destructuring_assignment`#![feature(destructuring_assignment)]`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