diff --git a/src/destructors.md b/src/destructors.md index 1ad3898a1d..1e03a318d0 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -266,6 +266,9 @@ smallest scope that contains the expression and is one of the following: > [!NOTE] > 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. +> [!NOTE] +> 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]. + r[destructors.scope.temporary.edition2024] > [!EDITION-2024] > 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: * The final expression of an extending [`if`] expression's consequent, `else if`, or `else` block. * An arm expression of an extending [`match`] expression. +> [!NOTE] +> 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]. + So the borrow expressions in `&mut 0`, `(&1, &mut 2)`, and `Some(&mut 3)` are all extending expressions. The borrows in `&0 + &1` and `f(&mut 0)` are not. @@ -640,6 +646,7 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi [binding modes]: patterns.md#binding-modes [closure]: types/closure.md [destructors]: destructors.md +[destructuring assignment]: expr.assign.destructure [expression]: expressions.md [identifier pattern]: patterns.md#identifier-patterns [initialized]: glossary.md#initialized diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index 35e715d400..a604c07cf7 100644 --- a/src/expressions/operator-expr.md +++ b/src/expressions/operator-expr.md @@ -862,6 +862,84 @@ r[expr.assign.destructure.discard-value] r[expr.assign.destructure.default-binding] Note that default binding modes do not apply for the desugared expression. +r[expr.assign.destructure.tmp-scopes] +> [!NOTE] +> The desugaring restricts the [temporary scope] of the assigned value operand (the RHS) of a destructuring assignment. +> +> 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. +> +> ```rust +> # fn temp() {} +> fn f(x: T) -> T { x } +> let x; +> (x = f(&temp()), x); // OK +> ``` +> +> 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. +> +> ```rust,compile_fail,E0716 +> # fn temp() {} +> # fn f(x: T) -> T { x } +> # let x; +> [x] = [f(&temp())]; // ERROR +> ``` +> +> This desugars to: +> +> ```rust,compile_fail,E0716 +> # fn temp() {} +> # fn f(x: T) -> T { x } +> # let x; +> { +> let [_x] = [f(&temp())]; +> // ^ +> // The temporary is dropped here. +> x = _x; // ERROR +> } +> ``` + +r[expr.assign.destructure.tmp-ext] +> [!NOTE] +> Due to the desugaring, the assigned value operand (the RHS) of a destructuring assignment is an [extending expression] within a newly-introduced block. +> +> Below, because the [temporary scope] is extended to the end of this introduced block, the assignment is allowed. +> +> ```rust +> # fn temp() {} +> # let x; +> [x] = [&temp()]; // OK +> ``` +> +> This desugars to: +> +> ```rust +> # fn temp() {} +> # let x; +> { let [_x] = [&temp()]; x = _x; } // OK +> ``` +> +> 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. +> +> ```rust,compile_fail,E0716 +> # fn temp() {} +> # let x; +> ([x] = [&temp()], x); // ERROR +> ``` +> +> This desugars to: +> +> ```rust,compile_fail,E0716 +> # fn temp() {} +> # let x; +> ( +> { +> let [_x] = [&temp()]; +> x = _x; +> }, // <-- The temporary is dropped here. +> x, // ERROR +> ); +> ``` + r[expr.compound-assign] ## Compound assignment expressions @@ -1011,6 +1089,7 @@ As with normal assignment expressions, compound assignment expressions always pr [dropping]: ../destructors.md [eval order test]: https://github.com/rust-lang/rust/blob/1.58.0/src/test/ui/expr/compound-assignment/eval-order.rs [explicit discriminants]: ../items/enumerations.md#explicit-discriminants +[extending expression]: destructors.scope.lifetime-extension.exprs [field-less enums]: ../items/enumerations.md#field-less-enum [grouped expression]: grouped-expr.md [literal expression]: literal-expr.md#integer-literal-expressions @@ -1025,11 +1104,14 @@ As with normal assignment expressions, compound assignment expressions always pr [unit]: ../types/tuple.md [Unit-only enums]: ../items/enumerations.md#unit-only-enum [value expression]: ../expressions.md#place-expressions-and-value-expressions +[temporary lifetime extension]: destructors.scope.lifetime-extension +[temporary scope]: destructors.scope.temporary [temporary value]: ../expressions.md#temporaries [float-float]: https://github.com/rust-lang/rust/issues/15536 [Function pointer]: ../types/function-pointer.md [Function item]: ../types/function-item.md [receiver]: expr.method.intro +[temporary]: expr.temporary [undefined behavior]: ../behavior-considered-undefined.md [Underscore expressions]: ./underscore-expr.md [range expressions]: ./range-expr.md