-
Notifications
You must be signed in to change notification settings - Fork 554
Specify temporary lifetime extension through expressions #2051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something slightly weird here: the last expression of a |
||
* 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]. | ||
Comment on lines
+507
to
+508
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I struggled a bit with how to express this; in its current form it's a bit of a hack. Ideally, I feel like it wouldn't need to be a separate rule or to refer to the definition of scope nesting, but it's working around something subtle: by ensuring that expressions' temporary scopes are only extended by their scope-ancestors, we can work around const blocks having parent expressions that (to my understanding) shouldn't be considered ancestor scopes of the const block's body; temporaries extended by const blocks are extended to the end of the program1. Maybe there's a simpler way to express this, and regardless it could probably use an admonition. I do think that some sort of "extended by" or "extended through" or "extending based on" relation is necessary though, regardless of how exactly we choose to define/present it. I feel there's too much ambiguity if we can't precisely associate expressions we're extending the temporary scopes of with the scopes they're being extended to. Footnotes
|
||
|
||
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]). | ||
Comment on lines
+510
to
+514
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if there's a way to cut down on the duplication here. I felt these rules were necessary to be precise about where temporaries' scopes are extended to, but having them in the introduction to the lifetime extension feels necessary too. |
||
|
||
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]. | ||
Comment on lines
+516
to
+517
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alternative to de-duplicating the above two rules, maybe there should be a more detailed section alongside |
||
|
||
#### 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() }); | ||
``` | ||
Comment on lines
+566
to
+577
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some additional examples would probably be good. Maybe it would help to have one where temporaries are extended through a block but not to the end of a statement? That could also be used as a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it could also use some additional text (or even an admonition?) to make clear the interaction with |
||
|
||
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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A major change here: an extending expression is now any expression that preserves lifetime extension, defined non-inductively. I found this helps with generalizing the definition beyond
let
statement initializers, but I also often found myself having to refer to an expression being "extending when its parent is extending"; that's are now just an extending expression.