Skip to content

Commit 58e85ee

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.
1 parent 8b24e65 commit 58e85ee

File tree

2 files changed

+118
-21
lines changed

2 files changed

+118
-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 [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,11 @@ 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()); // Operand of borrow macro call expression.
534+
# x;
535+
# // TODO: Simplify after this PR lands:
536+
# // <https://github.com/rust-lang/rust/pull/145882>.
537+
let x = format_args!("{:?}{:?}", temp(), temp()); // As above.
540538
# x;
541539
//
542540
// All of the temporaries above are still live here.
@@ -597,6 +595,23 @@ let x = 'a: { break 'a &temp() }; // ERROR
597595
# x;
598596
```
599597

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

@@ -621,14 +636,14 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
621636

622637
[Assignment]: expressions/operator-expr.md#assignment-expressions
623638
[binding modes]: patterns.md#binding-modes
639+
[borrow macro call]: expr.borrow-macros
624640
[closure]: types/closure.md
625641
[destructors]: destructors.md
626642
[expression]: expressions.md
627643
[identifier pattern]: patterns.md#identifier-patterns
628644
[initialized]: glossary.md#initialized
629645
[interior mutability]: interior-mutability.md
630646
[lazy boolean expression]: expressions/operator-expr.md#lazy-boolean-operators
631-
[macro invocation]: macros.md#macro-invocation
632647
[non-unwinding ABI boundary]: items/functions.md#unwinding
633648
[panic]: panic.md
634649
[place context]: expressions.md#place-expressions-and-value-expressions
@@ -658,12 +673,19 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi
658673
[array repeat operands]: expr.array.repeat-operand
659674
[async block expression]: expr.block.async
660675
[block expression]: expressions/block-expr.md
661-
[borrow expression]: expressions/operator-expr.md#borrow-operators
676+
[borrow operator]: expr.operator.borrow
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: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,69 @@ 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 [temporary scopes] may be [extended]. These macros are *super macros*. [Invocations][macro invocations] of these macros are *super macro call expressions*. Arguments to these macros may be *super operands*. These macros may create *super temporaries*.
263+
264+
> [!NOTE]
265+
> When the super macro call expression is an [extending expression], super operands are [extending expressions] and the [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+
<!-- TODO: Simplify after https://github.com/rust-lang/rust/pull/145882 lands. -->
274+
```rust
275+
# fn temp() -> String { String::from("") }
276+
let x = format_args!("{}{}", temp(), temp());
277+
x; // <-- The temporaries are extended, allowing use here.
278+
```
279+
280+
r[expr.super-macros.format_args.super-temporaries]
281+
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 [temporary]. This is a *super temporary*.
282+
283+
The expansion of a call to [`format_args!`] sometimes creates other internal [temporaries]. These are *super temporaries*.
284+
285+
```rust,compile_fail,E0716
286+
let x = {
287+
// This call creates an internal temporary.
288+
let x = format_args!("{:?}", 0);
289+
x // <-- The temporary is extended, allowing its use here.
290+
}; // <-- The temporary is dropped here.
291+
x; // ERROR
292+
```
293+
294+
```rust
295+
// This call doesn't create an internal temporary.
296+
let x = { let x = format_args!("{}", 0); x };
297+
x; // OK
298+
```
299+
300+
> [!NOTE]
301+
> The details of when [`format_args!`] does or does not create internal temporaries are currently unspecified.
302+
303+
r[expr.super-macros.pin]
304+
#### `pin!`
305+
306+
r[expr.super-macros.pin.super-operands]
307+
The argument to [`pin!`] is a *super operand*.
308+
309+
r[expr.super-macros.pin.super-temporaries]
310+
The argument to [`pin!`] is a [value expression context] that creates a [temporary]. This is a *super temporary*.
311+
312+
```rust
313+
# use core::pin::pin;
314+
# fn temp() {}
315+
// The argument is evaluated into a super temporary.
316+
let x = pin!(temp());
317+
// The temporary is extended, allowing its use here.
318+
x; // OK
319+
```
320+
258321
r[expr.implicit-borrow]
259322
### Implicit Borrows
260323

@@ -311,30 +374,39 @@ They are never allowed before:
311374
[`Copy`]: special-types-and-traits.md#copy
312375
[`Drop`]: special-types-and-traits.md#drop
313376
[`if let`]: expressions/if-expr.md#if-let-patterns
377+
[`format_args!`]: core::format_args
378+
[`pin!`]: core::pin::pin
314379
[`Sized`]: special-types-and-traits.md#sized
315380
[`while let`]: expressions/loop-expr.md#while-let-patterns
316381
[array expressions]: expressions/array-expr.md
317382
[array indexing]: expressions/array-expr.md#array-and-slice-indexing-expressions
318383
[assign]: expressions/operator-expr.md#assignment-expressions
319384
[block expressions]: expressions/block-expr.md
320385
[borrow]: expressions/operator-expr.md#borrow-operators
386+
[borrow expressions]: expr.operator.borrow
321387
[call expressions]: expressions/call-expr.md
322388
[comparison]: expressions/operator-expr.md#comparison-operators
323389
[compound assignment]: expressions/operator-expr.md#compound-assignment-expressions
324390
[deref]: expressions/operator-expr.md#the-dereference-operator
325391
[destructors]: destructors.md
326392
[drop scope]: destructors.md#drop-scopes
393+
[extended]: destructors.scope.lifetime-extension
394+
[extending expression]: destructors.scope.lifetime-extension.exprs
395+
[extending expressions]: destructors.scope.lifetime-extension.exprs
327396
[field]: expressions/field-expr.md
328397
[functional update]: expressions/struct-expr.md#functional-update-syntax
329398
[implicit borrow]: #implicit-borrows
399+
[implicitly borrowed]: expr.implicit-borrow
330400
[implicitly mutably borrowed]: #implicit-borrows
331401
[interior mutability]: interior-mutability.md
332402
[let statement]: statements.md#let-statements
403+
[macro invocations]: macro.invocation
333404
[match]: expressions/match-expr.md
334405
[method-call]: expressions/method-call-expr.md
335406
[Mutable `static` items]: items/static-items.md#mutable-statics
336407
[Outer attributes]: attributes.md
337408
[paths]: expressions/path-expr.md
409+
[place expression contexts]: expr.place-value
338410
[promoted]: destructors.md#constant-promotion
339411
[Range]: expressions/range-expr.md
340412
[raw borrow]: expressions/operator-expr.md#raw-borrow-operators
@@ -344,10 +416,16 @@ They are never allowed before:
344416
[static variables]: items/static-items.md
345417
[struct]: expressions/struct-expr.md
346418
[Structs]: expr.struct
419+
[temporaries]: expr.temporary
420+
[temporary scope]: destructors.scope.temporary
421+
[temporary scopes]: destructors.scope.temporary
422+
[temporary]: expr.temporary
347423
[Temporary values]: #temporaries
348424
[tuple expressions]: expressions/tuple-expr.md
349425
[Tuple structs]: items.struct.tuple
350426
[Tuples]: expressions/tuple-expr.md
351427
[Underscores]: expressions/underscore-expr.md
352428
[Unit structs]: items.struct.unit
429+
[value expression context]: expr.place-value
430+
[value expression]: expr.place-value
353431
[Variables]: variables.md

0 commit comments

Comments
 (0)