Skip to content

Commit 8c15a25

Browse files
committed
Revise text on built-in macro lifetime extension
Rather than discussing the built-in macros directly in the context of extending expressions, let's define "super macros", "super operands", and "super temporaries". It's unfortunate to have to introduce so many terms, but it still seems a bit clearer as the terms help to disentangle the many different things at play. Since the fix to `format_args!` hasn't landed yet, we'll state the intended rule and leave a note about the current situation.
1 parent 7e56e55 commit 8c15a25

File tree

2 files changed

+142
-21
lines changed

2 files changed

+142
-21
lines changed

src/destructors.md

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -398,11 +398,7 @@ println!("{:?}", C);
398398
```
399399

400400
r[destructors.scope.lifetime-extension.sub-expressions]
401-
If a [borrow][borrow expression], [dereference][dereference expression],
402-
[field][field expression], or [tuple indexing expression] has an extended
403-
temporary scope then so does its operand. If an [indexing expression] has an
404-
extended temporary scope then the indexed expression also has an extended
405-
temporary scope.
401+
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.
406402

407403
r[destructors.scope.lifetime-extension.patterns]
408404
#### Extending based on patterns
@@ -479,26 +475,24 @@ For a let statement with an initializer, an *extending expression* is an
479475
expression which is one of the following:
480476

481477
* The initializer expression.
482-
* The operand of an extending [borrow expression].
478+
* The operand of an extending [borrow] expression.
479+
* The [super operands] of an extending [super macro call] expression.
483480
* The operand(s) of an extending [array][array expression], [cast][cast
484481
expression], [braced struct][struct expression], or [tuple][tuple expression]
485482
expression.
486483
* The arguments to an extending [tuple struct] or [tuple variant] constructor expression.
487484
* The final expression of an extending [block expression] except for an [async block expression].
488485
* The final expression of an extending [`if`] expression's consequent, `else if`, or `else` block.
489486
* An arm expression of an extending [`match`] expression.
490-
* The argument(s) to an extending [`pin!`] or [`format_args!`] [macro invocation] expression.
491487

492488
So the borrow expressions in `&mut 0`, `(&1, &mut 2)`, and `Some(&mut 3)`
493489
are all extending expressions. The borrows in `&0 + &1` and `f(&mut 0)` are not.
494490

495-
r[destructors.scope.lifetime-extension.exprs.borrow]
496-
The operand of any extending borrow expression has its temporary scope
497-
extended.
491+
r[destructors.scope.lifetime-extension.exprs.borrows]
492+
The operand of an extending [borrow] expression has its [temporary scope] [extended].
498493

499-
r[destructors.scope.lifetime-extension.exprs.macros]
500-
The built-in macros [`pin!`] and [`format_args!`] create temporaries.
501-
Any extending [`pin!`] or [`format_args!`] [macro invocation] expression has an extended temporary scope.
494+
r[destructors.scope.lifetime-extension.exprs.super-macros]
495+
The [super temporaries] of an extending [super macro call] expression have their [scopes][temporary scopes] [extended].
502496

503497
> [!NOTE]
504498
> `rustc` does not treat [array repeat operands] of extending [array] expressions as extending expressions. Whether it should is an open question.
@@ -510,10 +504,10 @@ Any extending [`pin!`] or [`format_args!`] [macro invocation] expression has an
510504
Here are some examples where expressions have extended temporary scopes:
511505

512506
```rust,edition2024
507+
# use core::pin::pin;
513508
# use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
514-
# use std::pin::pin;
515509
# static X: AtomicU64 = AtomicU64::new(0);
516-
# struct S;
510+
# #[derive(Debug)] struct S;
517511
# impl Drop for S { fn drop(&mut self) { X.fetch_add(1, Relaxed); } }
518512
# const fn temp() -> S { S }
519513
let x = &temp(); // Operand of borrow.
@@ -536,7 +530,13 @@ let x = if true { &temp() } else { &temp() };
536530
# x;
537531
let x = match () { _ => &temp() }; // `match` arm expression.
538532
# x;
539-
let x = pin!(&temp()); // Argument to `pin!`.
533+
let x = pin!(temp()); // Super operand of super macro call expression.
534+
# x;
535+
let x = pin!({ &mut temp() }); // As above.
536+
# x;
537+
# // FIXME: Simplify after this PR lands:
538+
# // <https://github.com/rust-lang/rust/pull/145882>.
539+
let x = format_args!("{:?}{:?}", (), temp()); // As above.
540540
# x;
541541
//
542542
// All of the temporaries above are still live here.
@@ -597,6 +597,23 @@ let x = 'a: { break 'a &temp() }; // ERROR
597597
# x;
598598
```
599599

600+
```rust,edition2024,compile_fail,E0716
601+
# use core::pin::pin;
602+
# fn temp() {}
603+
// The argument to `pin!` is only an extending expression if the call
604+
// is an extending expression. Since it's not, the inner block is not
605+
// an extending expression, so the temporaries in its trailing
606+
// expression are dropped immediately.
607+
pin!({ &temp() }); // ERROR
608+
```
609+
610+
<!-- FIXME: Simplify after https://github.com/rust-lang/rust/pull/145882 lands. -->
611+
```rust,edition2024,compile_fail,E0716
612+
# fn temp() {}
613+
// As above.
614+
format_args!("{:?}{:?}", (), { &temp() }); // ERROR
615+
```
616+
600617
r[destructors.forget]
601618
## Not running destructors
602619

@@ -628,7 +645,6 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
628645
[initialized]: glossary.md#initialized
629646
[interior mutability]: interior-mutability.md
630647
[lazy boolean expression]: expressions/operator-expr.md#lazy-boolean-operators
631-
[macro invocation]: macros.md#macro-invocation
632648
[non-unwinding ABI boundary]: items/functions.md#unwinding
633649
[panic]: panic.md
634650
[place context]: expressions.md#place-expressions-and-value-expressions
@@ -658,12 +674,18 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
658674
[array repeat operands]: expr.array.repeat-operand
659675
[async block expression]: expr.block.async
660676
[block expression]: expressions/block-expr.md
661-
[borrow expression]: expressions/operator-expr.md#borrow-operators
677+
[borrow]: expr.operator.borrow
662678
[cast expression]: expressions/operator-expr.md#type-cast-expressions
663679
[dereference expression]: expressions/operator-expr.md#the-dereference-operator
680+
[extended]: destructors.scope.lifetime-extension
664681
[field expression]: expressions/field-expr.md
665682
[indexing expression]: expressions/array-expr.md#array-and-slice-indexing-expressions
666683
[struct expression]: expressions/struct-expr.md
684+
[super macro call]: expr.super-macros
685+
[super operands]: expr.super-macros
686+
[super temporaries]: expr.super-macros
687+
[temporary scope]: destructors.scope.temporary
688+
[temporary scopes]: destructors.scope.temporary
667689
[tuple expression]: expressions/tuple-expr.md#tuple-expressions
668690
[tuple indexing expression]: expressions/tuple-expr.md#tuple-indexing-expressions
669691

@@ -675,6 +697,3 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
675697
[`match`]: expressions/match-expr.md
676698
[`while let`]: expressions/loop-expr.md#while-let-patterns
677699
[`while`]: expressions/loop-expr.md#predicate-loops
678-
679-
[`pin!`]: std::pin::pin
680-
[`format_args!`]: core::format_args

src/expressions.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,92 @@ When using a value expression in most place expression contexts, a temporary unn
255255
The expression evaluates to that location instead, except if [promoted] to a `static`.
256256
The [drop scope] of the temporary is usually the end of the enclosing statement.
257257

258+
r[expr.super-macros]
259+
### Super macros
260+
261+
r[expr.super-macros.intro]
262+
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*.
263+
264+
> [!NOTE]
265+
> 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].
266+
267+
r[expr.super-macros.format_args]
268+
#### `format_args!`
269+
270+
r[expr.super-macros.format_args.super-operands]
271+
Except for the format string argument, all arguments passed to [`format_args!`] are *super operands*.
272+
273+
<!-- FIXME: Remove after https://github.com/rust-lang/rust/pull/145882 lands. -->
274+
> [!NOTE]
275+
> When there is only one placeholder, `rustc` does not yet treat the corresponding argument as a super operand.
276+
>
277+
> For details, see Rust issue [#145880](https://github.com/rust-lang/rust/issues/145880).
278+
279+
<!-- FIXME: Simplify after https://github.com/rust-lang/rust/pull/145882 lands. -->
280+
```rust,edition2024
281+
# fn temp() -> String { String::from("") }
282+
// Due to the call being an extending expression and the argument
283+
// being a super operand, the inner block is an extending expression,
284+
// so the scope of the temporary created in its trailing expression
285+
// is extended.
286+
let _ = format_args!("{:?}{}", (), { &temp() }); // OK
287+
```
288+
289+
r[expr.super-macros.format_args.super-temporaries]
290+
The super operands of [`format_args!`] are [implicitly borrowed] and are therefore [place expression contexts]. When a [value expression] is passed as an argument, it creates a *super temporary*.
291+
292+
<!-- FIXME: Simplify after https://github.com/rust-lang/rust/pull/145882 lands. -->
293+
```rust
294+
# fn temp() -> String { String::from("") }
295+
let x = format_args!("{}{}", temp(), temp());
296+
x; // <-- The temporaries are extended, allowing use here.
297+
```
298+
299+
The expansion of a call to [`format_args!`] sometimes creates other internal *super temporaries*.
300+
301+
```rust,compile_fail,E0716
302+
let x = {
303+
// This call creates an internal temporary.
304+
let x = format_args!("{:?}", 0);
305+
x // <-- The temporary is extended, allowing its use here.
306+
}; // <-- The temporary is dropped here.
307+
x; // ERROR
308+
```
309+
310+
```rust
311+
// This call doesn't create an internal temporary.
312+
let x = { let x = format_args!("{}", 0); x };
313+
x; // OK
314+
```
315+
316+
> [!NOTE]
317+
> The details of when [`format_args!`] does or does not create internal temporaries are currently unspecified.
318+
319+
r[expr.super-macros.pin]
320+
#### `pin!`
321+
322+
r[expr.super-macros.pin.super-operands]
323+
The argument to [`pin!`] is a *super operand*.
324+
325+
```rust,edition2024
326+
# use core::pin::pin;
327+
# fn temp() {}
328+
// As above for `format_args!`.
329+
let _ = pin!({ &temp() }); // OK
330+
```
331+
332+
r[expr.super-macros.pin.super-temporaries]
333+
The argument to [`pin!`] is a [value expression context] and creates a *super temporary*.
334+
335+
```rust
336+
# use core::pin::pin;
337+
# fn temp() {}
338+
// The argument is evaluated into a super temporary.
339+
let x = pin!(temp());
340+
// The temporary is extended, allowing its use here.
341+
x; // OK
342+
```
343+
258344
r[expr.implicit-borrow]
259345
### Implicit Borrows
260346

@@ -285,6 +371,7 @@ Implicit borrows may be taken in the following expressions:
285371
* Operand of the [dereference operator][deref] (`*`).
286372
* Operands of [comparison].
287373
* Left operands of the [compound assignment].
374+
* Arguments to [`format_args!`] except the format string.
288375

289376
r[expr.overload]
290377
## Overloading Traits
@@ -311,30 +398,39 @@ They are never allowed before:
311398
[`Copy`]: special-types-and-traits.md#copy
312399
[`Drop`]: special-types-and-traits.md#drop
313400
[`if let`]: expressions/if-expr.md#if-let-patterns
401+
[`format_args!`]: core::format_args
402+
[`pin!`]: core::pin::pin
314403
[`Sized`]: special-types-and-traits.md#sized
315404
[`while let`]: expressions/loop-expr.md#while-let-patterns
316405
[array expressions]: expressions/array-expr.md
317406
[array indexing]: expressions/array-expr.md#array-and-slice-indexing-expressions
318407
[assign]: expressions/operator-expr.md#assignment-expressions
319408
[block expressions]: expressions/block-expr.md
320409
[borrow]: expressions/operator-expr.md#borrow-operators
410+
[borrow expressions]: expr.operator.borrow
321411
[call expressions]: expressions/call-expr.md
322412
[comparison]: expressions/operator-expr.md#comparison-operators
323413
[compound assignment]: expressions/operator-expr.md#compound-assignment-expressions
324414
[deref]: expressions/operator-expr.md#the-dereference-operator
325415
[destructors]: destructors.md
326416
[drop scope]: destructors.md#drop-scopes
417+
[extended]: destructors.scope.lifetime-extension
418+
[extending expression]: destructors.scope.lifetime-extension.exprs
419+
[extending expressions]: destructors.scope.lifetime-extension.exprs
327420
[field]: expressions/field-expr.md
328421
[functional update]: expressions/struct-expr.md#functional-update-syntax
329422
[implicit borrow]: #implicit-borrows
423+
[implicitly borrowed]: expr.implicit-borrow
330424
[implicitly mutably borrowed]: #implicit-borrows
331425
[interior mutability]: interior-mutability.md
332426
[let statement]: statements.md#let-statements
427+
[macro invocations]: macro.invocation
333428
[match]: expressions/match-expr.md
334429
[method-call]: expressions/method-call-expr.md
335430
[Mutable `static` items]: items/static-items.md#mutable-statics
336431
[Outer attributes]: attributes.md
337432
[paths]: expressions/path-expr.md
433+
[place expression contexts]: expr.place-value
338434
[promoted]: destructors.md#constant-promotion
339435
[Range]: expressions/range-expr.md
340436
[raw borrow]: expressions/operator-expr.md#raw-borrow-operators
@@ -344,10 +440,16 @@ They are never allowed before:
344440
[static variables]: items/static-items.md
345441
[struct]: expressions/struct-expr.md
346442
[Structs]: expr.struct
443+
[temporaries]: expr.temporary
444+
[temporary scope]: destructors.scope.temporary
445+
[temporary scopes]: destructors.scope.temporary
446+
[temporary]: expr.temporary
347447
[Temporary values]: #temporaries
348448
[tuple expressions]: expressions/tuple-expr.md
349449
[Tuple structs]: items.struct.tuple
350450
[Tuples]: expressions/tuple-expr.md
351451
[Underscores]: expressions/underscore-expr.md
352452
[Unit structs]: items.struct.unit
453+
[value expression context]: expr.place-value
454+
[value expression]: expr.place-value
353455
[Variables]: variables.md

0 commit comments

Comments
 (0)