Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/destructors.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ let &ref x = &*&temp(); // OK
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:

Expand All @@ -486,13 +487,19 @@ expression which is one of the following:
* 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 argument(s) to an extending [`pin!`] or [`format_args!`] [macro invocation] expression.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this should be clearer about the format arguments being extended, not the format string (which doesn't need extension), but I've had trouble including that detail without breaking the flow/clarity of the section


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.borrow]
The operand of any extending borrow expression has its temporary scope
extended.

r[destructors.scope.lifetime-extension.exprs.macros]
The built-in macros [`pin!`] and [`format_args!`] create temporaries.
Any extending [`pin!`] or [`format_args!`] [macro invocation] expression has an extended temporary scope.
Comment on lines +495 to +501
Copy link
Contributor Author

@dianne dianne Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fourth pass: I've broken up destructors.scope.lifetime-extension.exprs to match destructors.scope.lifetime-extension.patterns and to put the rule for built-in macros' temporaries in a subsection. I've also moved the rule for arguments' extension back into the the newly-delimited rule destructors.scope.lifetime-extension.exprs.extending. I'm not satisfied with the wording yet, but structurally I think it's an improvement.

I'm doing a bit of conflation here. The "temporaries" here are both:

  • super let bindings; since they have (extended) temporary scopes, I feel like referring to them as "temporaries" is most fitting for the moment.
  • The borrowed temporaries created when a value expression is passed to format_args!.

Let me know if it needs further clarification. My hope is that it's a suitable level of detail for how these macros behave, to avoid specifying their exact expansion.


> [!NOTE]
> `rustc` does not treat [array repeat operands] of extending [array] expressions as extending expressions. Whether it should is an open question.
>
Expand All @@ -504,6 +511,7 @@ Here are some examples where expressions have extended temporary scopes:

```rust,edition2024
# use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
# use std::pin::pin;
# static X: AtomicU64 = AtomicU64::new(0);
# struct S;
# impl Drop for S { fn drop(&mut self) { X.fetch_add(1, Relaxed); } }
Expand All @@ -528,6 +536,8 @@ let x = if true { &temp() } else { &temp() };
# x;
let x = match () { _ => &temp() }; // `match` arm expression.
# x;
let x = pin!(&temp()); // Argument to `pin!`.
# x;
//
// All of the temporaries above are still live here.
# assert_eq!(0, X.load(Relaxed));
Expand Down Expand Up @@ -618,6 +628,7 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
[initialized]: glossary.md#initialized
[interior mutability]: interior-mutability.md
[lazy boolean expression]: expressions/operator-expr.md#lazy-boolean-operators
[macro invocation]: macros.md#macro-invocation
[non-unwinding ABI boundary]: items/functions.md#unwinding
[panic]: panic.md
[place context]: expressions.md#place-expressions-and-value-expressions
Expand Down Expand Up @@ -664,3 +675,6 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
[`match`]: expressions/match-expr.md
[`while let`]: expressions/loop-expr.md#while-let-patterns
[`while`]: expressions/loop-expr.md#predicate-loops

[`pin!`]: std::pin::pin
[`format_args!`]: core::format_args