Skip to content

Commit 63b9195

Browse files
authored
Merge pull request #1992 from dianne/destructuring-temp-scope
Document temporary scoping for destructuring assignments
2 parents 268200d + 32363c9 commit 63b9195

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

src/destructors.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ smallest scope that contains the expression and is one of the following:
266266
> [!NOTE]
267267
> The [scrutinee] of a `match` expression is not a temporary scope, so temporaries in the scrutinee can be dropped after the `match` expression. For example, the temporary for `1` in `match 1 { ref mut z => z };` lives until the end of the statement.
268268
269+
> [!NOTE]
270+
> The desugaring of a [destructuring assignment] restricts the temporary scope of its assigned value operand (the RHS). For details, see [expr.assign.destructure.tmp-scopes].
271+
269272
r[destructors.scope.temporary.edition2024]
270273
> [!EDITION-2024]
271274
> The 2024 edition added two new temporary scope narrowing rules: `if let` temporaries are dropped before the `else` block, and temporaries of tail expressions of blocks are dropped immediately after the tail expression is evaluated.
@@ -485,6 +488,9 @@ expression which is one of the following:
485488
* The final expression of an extending [`if`] expression's consequent, `else if`, or `else` block.
486489
* An arm expression of an extending [`match`] expression.
487490

491+
> [!NOTE]
492+
> The desugaring of a [destructuring assignment] makes its assigned value operand (the RHS) an extending expression within a newly-introduced block. For details, see [expr.assign.destructure.tmp-ext].
493+
488494
So the borrow expressions in `&mut 0`, `(&1, &mut 2)`, and `Some(&mut 3)`
489495
are all extending expressions. The borrows in `&0 + &1` and `f(&mut 0)` are not.
490496

@@ -640,6 +646,7 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
640646
[binding modes]: patterns.md#binding-modes
641647
[closure]: types/closure.md
642648
[destructors]: destructors.md
649+
[destructuring assignment]: expr.assign.destructure
643650
[expression]: expressions.md
644651
[identifier pattern]: patterns.md#identifier-patterns
645652
[initialized]: glossary.md#initialized

src/expressions/operator-expr.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,84 @@ r[expr.assign.destructure.discard-value]
862862
r[expr.assign.destructure.default-binding]
863863
Note that default binding modes do not apply for the desugared expression.
864864

865+
r[expr.assign.destructure.tmp-scopes]
866+
> [!NOTE]
867+
> The desugaring restricts the [temporary scope] of the assigned value operand (the RHS) of a destructuring assignment.
868+
>
869+
> In a basic assignment, the [temporary] is dropped at the end of the enclosing temporary scope. Below, that's the statement. Therefore, the assignment and use is allowed.
870+
>
871+
> ```rust
872+
> # fn temp() {}
873+
> fn f<T>(x: T) -> T { x }
874+
> let x;
875+
> (x = f(&temp()), x); // OK
876+
> ```
877+
>
878+
> Conversely, in a destructuring assignment, the temporary is dropped at the end of the `let` statement in the desugaring. As that happens before we try to assign to `x`, below, it fails.
879+
>
880+
> ```rust,compile_fail,E0716
881+
> # fn temp() {}
882+
> # fn f<T>(x: T) -> T { x }
883+
> # let x;
884+
> [x] = [f(&temp())]; // ERROR
885+
> ```
886+
>
887+
> This desugars to:
888+
>
889+
> ```rust,compile_fail,E0716
890+
> # fn temp() {}
891+
> # fn f<T>(x: T) -> T { x }
892+
> # let x;
893+
> {
894+
> let [_x] = [f(&temp())];
895+
> // ^
896+
> // The temporary is dropped here.
897+
> x = _x; // ERROR
898+
> }
899+
> ```
900+
901+
r[expr.assign.destructure.tmp-ext]
902+
> [!NOTE]
903+
> Due to the desugaring, the assigned value operand (the RHS) of a destructuring assignment is an [extending expression] within a newly-introduced block.
904+
>
905+
> Below, because the [temporary scope] is extended to the end of this introduced block, the assignment is allowed.
906+
>
907+
> ```rust
908+
> # fn temp() {}
909+
> # let x;
910+
> [x] = [&temp()]; // OK
911+
> ```
912+
>
913+
> This desugars to:
914+
>
915+
> ```rust
916+
> # fn temp() {}
917+
> # let x;
918+
> { let [_x] = [&temp()]; x = _x; } // OK
919+
> ```
920+
>
921+
> However, if we try to use `x`, even within the same statement, we'll get an error because the [temporary] is dropped at the end of this introduced block.
922+
>
923+
> ```rust,compile_fail,E0716
924+
> # fn temp() {}
925+
> # let x;
926+
> ([x] = [&temp()], x); // ERROR
927+
> ```
928+
>
929+
> This desugars to:
930+
>
931+
> ```rust,compile_fail,E0716
932+
> # fn temp() {}
933+
> # let x;
934+
> (
935+
> {
936+
> let [_x] = [&temp()];
937+
> x = _x;
938+
> }, // <-- The temporary is dropped here.
939+
> x, // ERROR
940+
> );
941+
> ```
942+
865943
r[expr.compound-assign]
866944
## Compound assignment expressions
867945
@@ -1011,6 +1089,7 @@ As with normal assignment expressions, compound assignment expressions always pr
10111089
[dropping]: ../destructors.md
10121090
[eval order test]: https://github.com/rust-lang/rust/blob/1.58.0/src/test/ui/expr/compound-assignment/eval-order.rs
10131091
[explicit discriminants]: ../items/enumerations.md#explicit-discriminants
1092+
[extending expression]: destructors.scope.lifetime-extension.exprs
10141093
[field-less enums]: ../items/enumerations.md#field-less-enum
10151094
[grouped expression]: grouped-expr.md
10161095
[literal expression]: literal-expr.md#integer-literal-expressions
@@ -1025,11 +1104,14 @@ As with normal assignment expressions, compound assignment expressions always pr
10251104
[unit]: ../types/tuple.md
10261105
[Unit-only enums]: ../items/enumerations.md#unit-only-enum
10271106
[value expression]: ../expressions.md#place-expressions-and-value-expressions
1107+
[temporary lifetime extension]: destructors.scope.lifetime-extension
1108+
[temporary scope]: destructors.scope.temporary
10281109
[temporary value]: ../expressions.md#temporaries
10291110
[float-float]: https://github.com/rust-lang/rust/issues/15536
10301111
[Function pointer]: ../types/function-pointer.md
10311112
[Function item]: ../types/function-item.md
10321113
[receiver]: expr.method.intro
1114+
[temporary]: expr.temporary
10331115
[undefined behavior]: ../behavior-considered-undefined.md
10341116
[Underscore expressions]: ./underscore-expr.md
10351117
[range expressions]: ./range-expr.md

0 commit comments

Comments
 (0)