From 7e56e558407d6e478a6f281f0066704dc35b7160 Mon Sep 17 00:00:00 2001 From: dianne Date: Tue, 26 Aug 2025 08:50:57 -0700 Subject: [PATCH 1/2] specify lifetime extension of `pin!` and `format_args!` arguments --- src/destructors.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/destructors.md b/src/destructors.md index 5150e2e0b..a590d4510 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -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: @@ -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. 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. + > [!NOTE] > `rustc` does not treat [array repeat operands] of extending [array] expressions as extending expressions. Whether it should is an open question. > @@ -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); } } @@ -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)); @@ -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 @@ -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 From abedccfeb2dc1d932e258088eee4b2fbca803f31 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 8 Sep 2025 00:13:24 +0000 Subject: [PATCH 2/2] 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. --- src/destructors.md | 61 ++++++++++++++++++---------- src/expressions.md | 99 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 21 deletions(-) diff --git a/src/destructors.md b/src/destructors.md index a590d4510..1ad3898a1 100644 --- a/src/destructors.md +++ b/src/destructors.md @@ -398,11 +398,7 @@ println!("{:?}", C); ``` r[destructors.scope.lifetime-extension.sub-expressions] -If a [borrow][borrow expression], [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. +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. r[destructors.scope.lifetime-extension.patterns] #### Extending based on patterns @@ -479,7 +475,8 @@ For a let statement with an initializer, an *extending expression* is an expression which is one of the following: * The initializer expression. -* The operand of an extending [borrow 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 expression], [braced struct][struct expression], or [tuple][tuple expression] expression. @@ -487,18 +484,15 @@ 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. 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.borrows] +The operand of an 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. +r[destructors.scope.lifetime-extension.exprs.super-macros] +The [super temporaries] of an extending [super macro call] expression have their [scopes][temporary scopes] [extended]. > [!NOTE] > `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 Here are some examples where expressions have extended temporary scopes: ```rust,edition2024 +# use core::pin::pin; # use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; -# use std::pin::pin; # static X: AtomicU64 = AtomicU64::new(0); -# struct S; +# #[derive(Debug)] struct S; # impl Drop for S { fn drop(&mut self) { X.fetch_add(1, Relaxed); } } # const fn temp() -> S { S } let x = &temp(); // Operand of borrow. @@ -536,7 +530,13 @@ let x = if true { &temp() } else { &temp() }; # x; let x = match () { _ => &temp() }; // `match` arm expression. # x; -let x = pin!(&temp()); // Argument to `pin!`. +let x = pin!(temp()); // Super operand of super macro call expression. +# x; +let x = pin!({ &mut temp() }); // As above. +# x; +# // FIXME: Simplify after this PR lands: +# // . +let x = format_args!("{:?}{:?}", (), temp()); // As above. # x; // // All of the temporaries above are still live here. @@ -597,6 +597,23 @@ 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 @@ -628,7 +645,6 @@ 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 @@ -658,12 +674,18 @@ There is one additional case to be aware of: when a panic reaches a [non-unwindi [array repeat operands]: expr.array.repeat-operand [async block expression]: expr.block.async [block expression]: expressions/block-expr.md -[borrow expression]: expressions/operator-expr.md#borrow-operators +[borrow]: expr.operator.borrow [cast expression]: expressions/operator-expr.md#type-cast-expressions [dereference expression]: expressions/operator-expr.md#the-dereference-operator +[extended]: destructors.scope.lifetime-extension [field expression]: expressions/field-expr.md [indexing expression]: expressions/array-expr.md#array-and-slice-indexing-expressions [struct expression]: expressions/struct-expr.md +[super macro call]: expr.super-macros +[super operands]: expr.super-macros +[super temporaries]: 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 @@ -675,6 +697,3 @@ 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 diff --git a/src/expressions.md b/src/expressions.md index eacf005e9..428745413 100644 --- a/src/expressions.md +++ b/src/expressions.md @@ -255,6 +255,92 @@ When using a value expression in most place expression contexts, a temporary unn The expression evaluates to that location instead, except if [promoted] to a `static`. The [drop scope] of the temporary is usually the end of the enclosing statement. +r[expr.super-macros] +### Super macros + +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]. + +r[expr.super-macros.format_args] +#### `format_args!` + +r[expr.super-macros.format_args.super-operands] +Except for the format string argument, all arguments passed to [`format_args!`] are *super operands*. + + +> [!NOTE] +> When there is only one placeholder, `rustc` does not yet treat the corresponding argument as a super operand. This is a bug. +> +> For details, see Rust issue [#145880](https://github.com/rust-lang/rust/issues/145880). + + +```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. +let _ = format_args!("{:?}{}", (), { &temp() }); // OK +``` + +r[expr.super-macros.format_args.super-temporaries] +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*. + + +```rust +# fn temp() -> String { String::from("") } +let x = format_args!("{}{}", temp(), temp()); +x; // <-- The temporaries are extended, allowing use here. +``` + +The expansion of a call to [`format_args!`] sometimes creates other internal *super temporaries*. + +```rust,compile_fail,E0716 +let x = { + // This call creates an internal temporary. + let x = format_args!("{:?}", 0); + x // <-- The temporary is extended, allowing its use here. +}; // <-- The temporary is dropped here. +x; // ERROR +``` + +```rust +// This call doesn't create an internal temporary. +let x = { let x = format_args!("{}", 0); x }; +x; // OK +``` + +> [!NOTE] +> The details of when [`format_args!`] does or does not create internal temporaries are currently unspecified. + +r[expr.super-macros.pin] +#### `pin!` + +r[expr.super-macros.pin.super-operands] +The argument to [`pin!`] is a *super operand*. + +```rust,edition2024 +# use core::pin::pin; +# fn temp() {} +// As above for `format_args!`. +let _ = pin!({ &temp() }); // OK +``` + +r[expr.super-macros.pin.super-temporaries] +The argument to [`pin!`] is a [value expression context] and creates a *super temporary*. + +```rust +# use core::pin::pin; +# fn temp() {} +// The argument is evaluated into a super temporary. +let x = pin!(temp()); +// The temporary is extended, allowing its use here. +x; // OK +``` + r[expr.implicit-borrow] ### Implicit Borrows @@ -285,6 +371,7 @@ Implicit borrows may be taken in the following expressions: * Operand of the [dereference operator][deref] (`*`). * Operands of [comparison]. * Left operands of the [compound assignment]. +* Arguments to [`format_args!`] except the format string. r[expr.overload] ## Overloading Traits @@ -311,6 +398,8 @@ They are never allowed before: [`Copy`]: special-types-and-traits.md#copy [`Drop`]: special-types-and-traits.md#drop [`if let`]: expressions/if-expr.md#if-let-patterns +[`format_args!`]: core::format_args +[`pin!`]: core::pin::pin [`Sized`]: special-types-and-traits.md#sized [`while let`]: expressions/loop-expr.md#while-let-patterns [array expressions]: expressions/array-expr.md @@ -324,17 +413,23 @@ They are never allowed before: [deref]: expressions/operator-expr.md#the-dereference-operator [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 [implicit borrow]: #implicit-borrows +[implicitly borrowed]: expr.implicit-borrow [implicitly mutably borrowed]: #implicit-borrows [interior mutability]: interior-mutability.md [let statement]: statements.md#let-statements +[macro invocations]: macro.invocation [match]: expressions/match-expr.md [method-call]: expressions/method-call-expr.md [Mutable `static` items]: items/static-items.md#mutable-statics [Outer attributes]: attributes.md [paths]: expressions/path-expr.md +[place expression contexts]: expr.place-value [promoted]: destructors.md#constant-promotion [Range]: expressions/range-expr.md [raw borrow]: expressions/operator-expr.md#raw-borrow-operators @@ -344,10 +439,14 @@ They are never allowed before: [static variables]: items/static-items.md [struct]: expressions/struct-expr.md [Structs]: expr.struct +[temporaries]: expr.temporary +[temporary scopes]: destructors.scope.temporary [Temporary values]: #temporaries [tuple expressions]: expressions/tuple-expr.md [Tuple structs]: items.struct.tuple [Tuples]: expressions/tuple-expr.md [Underscores]: expressions/underscore-expr.md [Unit structs]: items.struct.unit +[value expression context]: expr.place-value +[value expression]: expr.place-value [Variables]: variables.md