From e856ddfa450f47455539a32f1d9248fc3aefb347 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 12 Oct 2025 13:16:51 -0700 Subject: [PATCH 1/5] Specify lifetime extension through expressions --- src/destructors.md | 77 ++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/src/destructors.md b/src/destructors.md index 73b4d65948..ece348b21e 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -474,36 +474,47 @@ r[destructors.scope.lifetime-extension.exprs] #### Extending based on expressions r[destructors.scope.lifetime-extension.exprs.extending] -For a let statement with an initializer, an *extending expression* is an -expression which is one of the following: +An *extending expression* is an expression which is one of the following: -* The initializer expression. -* The operand of an extending [borrow] expression. -* The [super operands] of an extending [super macro call] expression. -* The operand(s) of an extending [array][array expression], [cast][cast +* The initializer expression of a `let` statement or the body expression of a [static][static item] or [constant item]. +* The operand of a [borrow] expression. +* The [super operands] of a [super macro call] expression. +* The operand(s) of an [array][array expression], [cast][cast expression], [braced struct][struct expression], or [tuple][tuple expression] expression. -* The arguments to an extending [tuple struct] or [tuple enum variant] constructor expression. -* The final expression of an extending [block expression] except for an [async block expression]. -* The final expression of an extending [`if`] expression's consequent, `else if`, or `else` block. -* An arm expression of an extending [`match`] expression. +* The arguments to a [tuple struct] or [tuple enum variant] constructor expression. +* The final expression of a [block expression] except for an [async block expression]. +* The final expression of an [`if`] expression's consequent, `else if`, or `else` block. +* An arm expression of a [`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)` +> [!NOTE] +> `rustc` does not treat [array repeat operands] of [array] expressions as extending expressions. Whether it should is an open question. +> +> For details, see [Rust issue #146092](https://github.com/rust-lang/rust/issues/146092). + +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. r[destructors.scope.lifetime-extension.exprs.borrows] -The operand of an extending [borrow] expression has its [temporary scope] [extended]. +The [temporary scope] of the operand of a [borrow] expression is *extended through* the scope of the borrow expression. r[destructors.scope.lifetime-extension.exprs.super-macros] -The [super temporaries] of an extending [super macro call] expression have their [scopes][temporary scopes] [extended]. +The [scopes][temporary scopes] of the [super temporaries] of an extending [super macro call] expression are *extended through* the scope of the super macro call expression. -> [!NOTE] -> `rustc` does not treat [array repeat operands] of extending [array] expressions as extending expressions. Whether it should is an open question. -> -> For details, see [Rust issue #146092](https://github.com/rust-lang/rust/issues/146092). +r[destructors.scope.lifetime-extension.exprs.parent] +If a temporary scope is extended through the scope of an extending expression, it is extended through that scope's [parent][destructors.scope.nesting]. + +r[destructors.scope.lifetime-extension.exprs.let] +A temporary scope extended through a `let` statement scope is [extended] to the scope of the block containing the `let` statement ([destructors.scope.lifetime-extension.let]). + +r[destructors.scope.lifetime-extension.exprs.static] +A temporary scope extended through a [static][static item] or [constant item] scope or a [const block][const block expression] scope is [extended] to the end of the program ([destructors.scope.lifetime-extension.static]). + +r[destructors.scope.lifetime-extension.exprs.other] +A temporary scope extended through the scope of a non-extending expression is [extended] to that expression's [temporary scope]. #### Examples @@ -552,6 +563,19 @@ let x = format_args!("{:?}", temp()); // As above. # assert_eq!(0, X.load(Relaxed)); ``` +```rust,edition2024 +# fn temp() {} +# fn use_temp(_: &()) {} +// The final expression of a block is extending. Since the block below +// is not itself extending, the temporary is extended to the block +// expression's temporary scope, ending at the semicolon. +use_temp({ &temp() }); +// As above, the final expressions of `if`/`else` blocks are +// extending, which extends the temporaries to the `if` expression's +// temporary scope. +use_temp(if true { &temp() } else { &temp() }); +``` + Here are some examples where expressions don't have extended temporary scopes: ```rust,compile_fail,E0716 @@ -606,22 +630,6 @@ let x = 'a: { break 'a &temp() }; // ERROR # x; ``` -```rust,edition2024,compile_fail,E0716 -# use core::pin::pin; -# fn temp() {} -// The argument to `pin!` is only an extending expression if the call -// is an extending expression. Since it's not, the inner block is not -// an extending expression, so the temporaries in its trailing -// expression are dropped immediately. -pin!({ &temp() }); // ERROR -``` - -```rust,edition2024,compile_fail,E0716 -# fn temp() {} -// As above. -format_args!("{:?}", { &temp() }); // ERROR -``` - r[destructors.forget] ## Not running destructors @@ -647,6 +655,7 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi [Assignment]: expressions/operator-expr.md#assignment-expressions [binding modes]: patterns.md#binding-modes [closure]: types/closure.md +[constant item]: items/constant-items.md [destructors]: destructors.md [destructuring assignment]: expr.assign.destructure [expression]: expressions.md @@ -660,6 +669,7 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi [promoted]: destructors.md#constant-promotion [scrutinee]: glossary.md#scrutinee [statement]: statements.md +[static item]: items/static-items.md [temporary]: expressions.md#temporaries [unwinding]: panic.md#unwinding [variable]: variables.md @@ -685,6 +695,7 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi [block expression]: expressions/block-expr.md [borrow]: expr.operator.borrow [cast expression]: expressions/operator-expr.md#type-cast-expressions +[const block expression]: expr.block.const [dereference expression]: expressions/operator-expr.md#the-dereference-operator [extended]: destructors.scope.lifetime-extension [field expression]: expressions/field-expr.md From 596d2824c75e96889aaa8f54bcb32e5a1332c331 Mon Sep 17 00:00:00 2001 From: dianne Date: Sat, 1 Nov 2025 21:29:29 -0700 Subject: [PATCH 2/5] Reframe lifetime extension to not be specific to `let` statements --- src/destructors.md | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/destructors.md b/src/destructors.md index ece348b21e..416378e5ef 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -375,11 +375,8 @@ r[destructors.scope.lifetime-extension] > [!NOTE] > The exact rules for temporary lifetime extension are subject to change. This is describing the current behavior only. -r[destructors.scope.lifetime-extension.let] -The temporary scopes for expressions in `let` statements are sometimes -*extended* to the scope of the block containing the `let` statement. This is -done when the usual temporary scope would be too small, based on certain -syntactic rules. For example: +r[destructors.scope.lifetime-extension.intro] +The temporary scopes for expressions are sometimes *extended*. This is done when the usual temporary scope would be too small, based on certain syntactic rules. For example: ```rust let x = &mut 0; @@ -388,18 +385,6 @@ let x = &mut 0; println!("{}", x); ``` -r[destructors.scope.lifetime-extension.static] -Lifetime extension also applies to `static` and `const` items, where it -makes temporaries live until the end of the program. For example: - -```rust -const C: &Vec = &Vec::new(); -// Usually this would be a dangling reference as the `Vec` would only -// exist inside the initializer expression of `C`, but instead the -// borrow gets lifetime-extended so it effectively has `'static` lifetime. -println!("{:?}", C); -``` - r[destructors.scope.lifetime-extension.sub-expressions] If a [borrow], [dereference][dereference expression], [field][field expression], or [tuple indexing expression] has an extended temporary scope, then so does its operand. If an [indexing expression] has an extended temporary scope, then the indexed expression also has an extended temporary scope. @@ -445,7 +430,7 @@ So `ref x`, `V(ref x)` and `[ref x, y]` are all extending patterns, but `x`, `&r r[destructors.scope.lifetime-extension.patterns.let] If the pattern in a `let` statement is an extending pattern then the temporary -scope of the initializer expression is extended. +scope of the initializer expression is extended to the scope of the block containing the `let` statement. ```rust # fn temp() {} @@ -508,10 +493,18 @@ r[destructors.scope.lifetime-extension.exprs.parent] If a temporary scope is extended through the scope of an extending expression, it is extended through that scope's [parent][destructors.scope.nesting]. r[destructors.scope.lifetime-extension.exprs.let] -A temporary scope extended through a `let` statement scope is [extended] to the scope of the block containing the `let` statement ([destructors.scope.lifetime-extension.let]). +A temporary scope extended through a `let` statement scope is [extended] to the scope of the block containing the `let` statement. r[destructors.scope.lifetime-extension.exprs.static] -A temporary scope extended through a [static][static item] or [constant item] scope or a [const block][const block expression] scope is [extended] to the end of the program ([destructors.scope.lifetime-extension.static]). +A temporary scope extended through a [static][static item] or [constant item] scope or a [const block][const block expression] scope is [extended] to the end of the program. + +```rust +const C: &Vec = &Vec::new(); +// Usually this would be a dangling reference as the `Vec` would only +// exist inside the initializer expression of `C`, but instead the +// borrow gets lifetime-extended so it effectively has `'static` lifetime. +println!("{:?}", C); +``` r[destructors.scope.lifetime-extension.exprs.other] A temporary scope extended through the scope of a non-extending expression is [extended] to that expression's [temporary scope]. From 71178bb5d37e27004464df50f8d759bf66cc3365 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 2 Nov 2025 22:09:47 -0800 Subject: [PATCH 3/5] Reorganize, remove hacks, clarify, and add examples --- src/destructors.md | 95 +++++++++++++++++++++++-------------- src/items/constant-items.md | 4 +- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/src/destructors.md b/src/destructors.md index 416378e5ef..883e69720a 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -458,19 +458,24 @@ let &ref x = &*&temp(); // OK r[destructors.scope.lifetime-extension.exprs] #### Extending based on expressions +r[destructors.scope.lifetime-extension.exprs.borrows] +The [temporary scope] of the operand of a [borrow] expression is the *extended scope* of the operand expression, defined below. + +r[destructors.scope.lifetime-extension.exprs.super-macros] +The [scope][temporary scope] of each [super temporary] of a [super macro call] expression is the extended scope of the super macro call expression. + r[destructors.scope.lifetime-extension.exprs.extending] -An *extending expression* is an expression which is one of the following: +The extended scope of an expression is defined in terms of *extending expressions* and their *extending parents*. An extending expression is an expression which is one of the following: -* The initializer expression of a `let` statement or the body expression of a [static][static item] or [constant item]. -* The operand of a [borrow] expression. -* The [super operands] of a [super macro call] expression. +* The operand of a [borrow] expression, the extending parent of which is the borrow expression. +* The [super operands] of a [super macro call] expression, the extending parent of which is the macro call expression. * The operand(s) of an [array][array expression], [cast][cast expression], [braced struct][struct expression], or [tuple][tuple expression] - expression. -* The arguments to a [tuple struct] or [tuple enum variant] constructor expression. -* The final expression of a [block expression] except for an [async block expression]. -* The final expression of an [`if`] expression's consequent, `else if`, or `else` block. -* An arm expression of a [`match`] expression. + expression, the extending parent of which is the array, cast, braced struct, or tuple expression. +* The arguments to a [tuple struct] or [tuple enum variant] constructor expression, the extending parent of which is the constructor expression. +* The final expression of a plain [block expression] or [`unsafe` block expression], the extending parent of which is the block expression. +* The final expression of an [`if`] expression's consequent, `else if`, or `else` block, the extending parent of which is the `if` expression. +* An arm expression of a [`match`] expression, the extending parent of which is the `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]. @@ -483,20 +488,32 @@ An *extending expression* is an expression which is one of the following: 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. -r[destructors.scope.lifetime-extension.exprs.borrows] -The [temporary scope] of the operand of a [borrow] expression is *extended through* the scope of the borrow expression. - -r[destructors.scope.lifetime-extension.exprs.super-macros] -The [scopes][temporary scopes] of the [super temporaries] of an extending [super macro call] expression are *extended through* the scope of the super macro call expression. - r[destructors.scope.lifetime-extension.exprs.parent] -If a temporary scope is extended through the scope of an extending expression, it is extended through that scope's [parent][destructors.scope.nesting]. +The extended scope of an extending expression is the extended scope of its extending parent. r[destructors.scope.lifetime-extension.exprs.let] -A temporary scope extended through a `let` statement scope is [extended] to the scope of the block containing the `let` statement. +The extended scope of the initializer expression of a `let` statement is the scope of the block containing the `let` statement. + +> [!EXAMPLE] +> In this example, the temporary value holding the result of `temp()` is extended to the end of the block in which `x` is declared: +> +> ```rust,edition2024 +> # fn temp() {} +> let x = { &temp() }; +> println!("{x:?}"); +> ``` +> +> `temp()` is the operand of a borrow expression, so its temporary scope is its extended scope. +> To determine its extended scope, look outward: +> +> * Since borrow expressions' operands are extending, the extended scope of `temp()` is the extended scope of its extending parent, the borrow expression. +> * `&temp()` is the final expression of a plain block. Since the final expressions of plain blocks are extending, the extended temporary scope of `&temp()` is the extended scope of its extending parent, the block expression. +> * `{ &temp() }` is the initializer expression of a `let` statement, so its extended scope is the scope of the block containg that `let` statement. +> +> If not for temporary lifetime extension, the result of `temp()` would be dropped after evaluating the tail expression of the block `{ &temp() }` ([destructors.scope.temporary.enclosing]). r[destructors.scope.lifetime-extension.exprs.static] -A temporary scope extended through a [static][static item] or [constant item] scope or a [const block][const block expression] scope is [extended] to the end of the program. +The extended scope of the body expression of a [static][static item] or [constant item], and of the final expression of a [const block expression], is the entire program. This prevents destructors from being run. ```rust const C: &Vec = &Vec::new(); @@ -507,7 +524,29 @@ println!("{:?}", C); ``` r[destructors.scope.lifetime-extension.exprs.other] -A temporary scope extended through the scope of a non-extending expression is [extended] to that expression's [temporary scope]. +The extended scope of any other expression is its [temporary scope]. + +> [!NOTE] +> In this case, the expression is not extending, meaning it cannot be a borrow expression or a [super operand][super operands] to a [super macro call] expression, so its temporary scope is given by [destructors.scope.temporary.enclosing]. + +> [!EXAMPLE] +> In this example, the temporary value holding the result of `temp()` is extended to the end of the statement: +> +> ```rust,edition2024 +> # fn temp() {} +> # fn use_temp(_: &()) {} +> use_temp({ &temp() }); +> ``` +> +> `temp()` is the operand of a borrow expression, so its temporary scope is its extended scope. +> To determine its extended scope, look outward: +> +> * Since borrow expressions' operands are extending, the extended scope of `temp()` is the extended scope of its extending parent, the borrow expression. +> * `&temp()` is the final expression of a plain block. Since the final expressions of plain blocks are extending, the extended scope of `&temp()` is the extended scope of its extending parent, the block expression. +> * `{ &temp() }` is the argument of a call expression, which is not extending. Since no other cases apply, its extended scope is its temporary scope. +> * Per [destructors.scope.temporary.enclosing], the temporary scope of `{ &temp() }`, and thus the extended scope of `temp()`, is the scope of the statement. +> +> If not for temporary lifetime extension, the result of `temp()` would be dropped after evaluating the tail expression of the block `{ &temp() }` ([destructors.scope.temporary.enclosing]). #### Examples @@ -556,19 +595,6 @@ let x = format_args!("{:?}", temp()); // As above. # assert_eq!(0, X.load(Relaxed)); ``` -```rust,edition2024 -# fn temp() {} -# fn use_temp(_: &()) {} -// The final expression of a block is extending. Since the block below -// is not itself extending, the temporary is extended to the block -// expression's temporary scope, ending at the semicolon. -use_temp({ &temp() }); -// As above, the final expressions of `if`/`else` blocks are -// extending, which extends the temporaries to the `if` expression's -// temporary scope. -use_temp(if true { &temp() } else { &temp() }); -``` - Here are some examples where expressions don't have extended temporary scopes: ```rust,compile_fail,E0716 @@ -684,7 +710,6 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi [array expression]: expressions/array-expr.md#array-expressions [array repeat operands]: expr.array.repeat-operand -[async block expression]: expr.block.async [block expression]: expressions/block-expr.md [borrow]: expr.operator.borrow [cast expression]: expressions/operator-expr.md#type-cast-expressions @@ -696,11 +721,11 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi [struct expression]: expressions/struct-expr.md [super macro call]: expr.super-macros [super operands]: expr.super-macros -[super temporaries]: expr.super-macros +[super temporary]: expr.super-macros [temporary scope]: destructors.scope.temporary -[temporary scopes]: destructors.scope.temporary [tuple expression]: expressions/tuple-expr.md#tuple-expressions [tuple indexing expression]: expressions/tuple-expr.md#tuple-indexing-expressions +[`unsafe` block expression]: expr.block.unsafe [`for`]: expressions/loop-expr.md#iterator-loops [`if let`]: expressions/if-expr.md#if-let-patterns diff --git a/src/items/constant-items.md b/src/items/constant-items.md index 321894a944..6025695661 100644 --- a/src/items/constant-items.md +++ b/src/items/constant-items.md @@ -90,7 +90,7 @@ const _: &mut u8 = unsafe { &mut S }; // ERROR. > // the program. > ``` > -> Here, the value `0` is a temporary whose scope is extended to the end of the program (see [destructors.scope.lifetime-extension.static]). Such temporaries cannot be mutably borrowed in constant expressions (see [const-eval.const-expr.borrows]). +> Here, the value `0` is a temporary whose scope is extended to the end of the program (see [destructors.scope.lifetime-extension.exprs.static]). Such temporaries cannot be mutably borrowed in constant expressions (see [const-eval.const-expr.borrows]). > > To allow this, we'd have to decide whether each use of the constant creates a new `u8` value or whether each use shares the same lifetime-extended temporary. The latter choice, though closer to how `rustc` thinks about this today, would break the conceptual model that, in most cases, the constant initializer can be thought of as being inlined wherever the constant is used. Since we haven't decided, and due to the other problem mentioned, this is not allowed. @@ -175,7 +175,7 @@ const _: &&mut u8 = unsafe { &S }; // OK. > const _: &AtomicU8 = &AtomicU8::new(0); // ERROR. > ``` > -> Here, the `AtomicU8` is a temporary whose scope is extended to the end of the program (see [destructors.scope.lifetime-extension.static]). Such temporaries with interior mutability cannot be borrowed in constant expressions (see [const-eval.const-expr.borrows]). +> Here, the `AtomicU8` is a temporary whose scope is extended to the end of the program (see [destructors.scope.lifetime-extension.exprs.static]). Such temporaries with interior mutability cannot be borrowed in constant expressions (see [const-eval.const-expr.borrows]). > > To allow this, we'd have to decide whether each use of the constant creates a new `AtomicU8` or whether each use shares the same lifetime-extended temporary. The latter choice, though closer to how `rustc` thinks about this today, would break the conceptual model that, in most cases, the constant initializer can be thought of as being inlined wherever the constant is used. Since we haven't decided, this is not allowed. From a0ae157c1d987275857e5bea138e1997036bef4c Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 2 Nov 2025 22:16:23 -0800 Subject: [PATCH 4/5] Add a couple examples where the spec didn't change --- src/destructors.md | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/destructors.md b/src/destructors.md index 883e69720a..6bd69b72d6 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -388,6 +388,24 @@ println!("{}", x); r[destructors.scope.lifetime-extension.sub-expressions] If a [borrow], [dereference][dereference expression], [field][field expression], or [tuple indexing expression] has an extended temporary scope, then so does its operand. If an [indexing expression] has an extended temporary scope, then the indexed expression also has an extended temporary scope. +```rust +# use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; +# static X: AtomicU64 = AtomicU64::new(0); +# struct PrintOnDrop(&'static str); +# impl Drop for PrintOnDrop { +# fn drop(&mut self) { +# X.fetch_add(1, Relaxed); +# println!("{}", self.0); +# } +# } +let x = &(0, PrintOnDrop("tuple 1 dropped")).0; +let ref y = (0, PrintOnDrop("tuple 2 dropped")).0; +// Though only its first field is borrowed, the temporary for the entire tuple +// lives to the end of the block in both cases. +println!("{x}, {y}"); +# assert_eq!(0, X.load(Relaxed)); +``` + r[destructors.scope.lifetime-extension.patterns] #### Extending based on patterns @@ -516,11 +534,18 @@ r[destructors.scope.lifetime-extension.exprs.static] The extended scope of the body expression of a [static][static item] or [constant item], and of the final expression of a [const block expression], is the entire program. This prevents destructors from being run. ```rust -const C: &Vec = &Vec::new(); -// Usually this would be a dangling reference as the `Vec` would only -// exist inside the initializer expression of `C`, but instead the -// borrow gets lifetime-extended so it effectively has `'static` lifetime. +# #[derive(Debug)] struct PanicOnDrop; +# impl Drop for PanicOnDrop { fn drop(&mut self) { panic!() } } +# impl PanicOnDrop { const fn new() -> PanicOnDrop { PanicOnDrop } } +const C: &PanicOnDrop = &PanicOnDrop::new(); +// Usually this would be a dangling reference as the result of +// `PanicOnDrop::new()` would only exist inside the initializer expression of +// `C`, but instead the borrow gets lifetime-extended so it effectively has +// a `'static` lifetime and its destructor is never run. println!("{:?}", C); +// `const` blocks may likewise extend temporaries to the end of the program: +// the result of `PanicOnDrop::new()` is not dropped. +println!("{:?}", const { &PanicOnDrop::new() }); ``` r[destructors.scope.lifetime-extension.exprs.other] From e37d803fc558b400599a3dd3666004fe027be2e7 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 2 Nov 2025 23:25:47 -0800 Subject: [PATCH 5/5] Adjust other references to extending expressions --- src/expressions.md | 12 ++++++------ src/expressions/operator-expr.md | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/expressions.md b/src/expressions.md index 9abc51ae59..353993dc5f 100644 --- a/src/expressions.md +++ b/src/expressions.md @@ -262,7 +262,7 @@ r[expr.super-macros.intro] Certain built-in macros may create [temporaries] whose [scopes][temporary scopes] may be [extended]. These temporaries are *super temporaries* and these macros are *super macros*. [Invocations][macro invocations] of these macros are *super macro call expressions*. Arguments to these macros may be *super operands*. > [!NOTE] -> When a super macro call expression is an [extending expression], its super operands are [extending expressions] and the [scopes][temporary scopes] of the super temporaries are [extended]. See [destructors.scope.lifetime-extension.exprs]. +> The super operands of a super macro call are [extending expressions] and the [scopes][temporary scopes] of the super temporaries are [extended]. See [destructors.scope.lifetime-extension.exprs]. r[expr.super-macros.format_args] #### `format_args!` @@ -272,10 +272,11 @@ Except for the format string argument, all arguments passed to [`format_args!`] ```rust,edition2024 # fn temp() -> String { String::from("") } -// Due to the call being an extending expression and the argument -// being a super operand, the inner block is an extending expression, -// so the scope of the temporary created in its trailing expression -// is extended. +// Due to the argument being a super operand, the inner block is an +// extending expression, so the scope of the temporary created in its +// trailing expression is extended to the extended scope of the call. +// Since the call is the initializer of a `let` statement, this +// extends it to the end of the surrounding block. let _ = format_args!("{}", { &temp() }); // OK ``` @@ -406,7 +407,6 @@ They are never allowed before: [destructors]: destructors.md [drop scope]: destructors.md#drop-scopes [extended]: destructors.scope.lifetime-extension -[extending expression]: destructors.scope.lifetime-extension.exprs [extending expressions]: destructors.scope.lifetime-extension.exprs [field]: expressions/field-expr.md [functional update]: expressions/struct-expr.md#functional-update-syntax diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index fae78dd44e..257a8b161d 100644 --- a/src/expressions/operator-expr.md +++ b/src/expressions/operator-expr.md @@ -900,9 +900,9 @@ r[expr.assign.destructure.tmp-scopes] 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. +> Due to the desugaring, the assigned value operand (the RHS) of a destructuring assignment is the initializer expression of a `let` statement within a newly-introduced block. > -> Below, because the [temporary scope] is extended to the end of this introduced block, the assignment is allowed. +> Below, because the [temporary scope] is [extended] to the end of this introduced block, the assignment is allowed. > > ```rust > # fn temp() {} @@ -1089,7 +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 +[extended]: 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