From 9919169761c6f1c118c883b2e7f5b2449a9a7173 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 12 Oct 2025 13:16:51 -0700 Subject: [PATCH 1/6] 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 593f1ac2b5d1e9d1d05aa1ab476b7c2ac99bdc31 Mon Sep 17 00:00:00 2001 From: dianne Date: Sat, 1 Nov 2025 21:29:29 -0700 Subject: [PATCH 2/6] 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 51c2e99b3c6aa6e75ff846e85b2fabd8c20d0156 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 2 Nov 2025 22:09:47 -0800 Subject: [PATCH 3/6] Reorganize, remove hacks, clarify, and add examples --- src/destructors.md | 92 ++++++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/src/destructors.md b/src/destructors.md index 416378e5ef..66b8700800 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 *borrow 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 borrow 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 borrow 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 borrow scope of an extending expression is the borrow 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 borrow 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 borrow scope. +> To determine its borrow scope, look outward: +> +> * Since borrow expressions' operands are extending, the borrow scope of `temp()` is the borrow 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 borrow scope of its extending parent, the block expression. +> * `{ &temp() }` is the initializer expression of a `let` statement, so its borrow 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 borrow 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,26 @@ 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 borrow scope of any other expression is its non-extended temporary scope, as defined 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 borrow scope. +> To determine its borrow scope, look outward: +> +> * Since borrow expressions' operands are extending, the borrow scope of `temp()` is the borrow 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 borrow scope of `&temp()` is the borrow 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 borrow scope is its temporary scope. +> * Per [destructors.scope.temporary.enclosing], the temporary scope of `{ &temp() }`, and thus the borrow 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 +592,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 +707,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 +718,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 From 1b28276194d9c18dc9986a12de3c3780fea4d8c2 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 2 Nov 2025 22:16:23 -0800 Subject: [PATCH 4/6] 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 66b8700800..2e4a4f483b 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 borrow 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 084b8881bf0fafb4c097a8dbaf5f3619192c0b04 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 2 Nov 2025 23:25:47 -0800 Subject: [PATCH 5/6] 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 From 8cdf7dd6ed4a3f7b84dcd6be83c73364af25453d Mon Sep 17 00:00:00 2001 From: dianne Date: Tue, 2 Dec 2025 12:32:38 -0800 Subject: [PATCH 6/6] Extremely rough draft: remove sub-expressions ambiguity --- src/destructors.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/destructors.md b/src/destructors.md index 2e4a4f483b..9c4b55046a 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -386,7 +386,15 @@ 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. +A *place base context* is an expression context that is one of the following: + +- The operand of a [dereference][dereference expression], [field][field expression], or [tuple indexing expression]. +- The indexed expression of an [indexing expression]. +- The operand of a [borrow expression] in a place base context. + +The *projected expression* of an expression in a place base context is its closest ancestor that is not in a place base context. + +The temporary scope of an expression in a place base context is defined to be the temporary scope of its projected expression. ```rust # use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; @@ -477,7 +485,7 @@ 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 *borrow scope* of the operand expression, defined below. +The [temporary scope] of the operand of a [borrow expression] outside of a [place base context][destructors.scope.lifetime-extension.sub-expressions] is the *borrow 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 borrow scope of the super macro call expression. @@ -485,7 +493,7 @@ The [scope][temporary scope] of each [super temporary] of a [super macro call] e r[destructors.scope.lifetime-extension.exprs.extending] The borrow 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 operand of a [borrow] expression, the extending parent of which is the borrow 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] @@ -733,7 +741,7 @@ 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 [block expression]: expressions/block-expr.md -[borrow]: expr.operator.borrow +[borrow expression]: 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