diff --git a/src/destructors.md b/src/destructors.md index 5150e2e0b..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 @@ -474,11 +470,13 @@ 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: * 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. @@ -490,8 +488,11 @@ expression which is one of the following: 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. -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.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. @@ -503,9 +504,10 @@ extended. Here are some examples where expressions have extended temporary scopes: ```rust,edition2024 +# use core::pin::pin; # use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; # 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. @@ -528,6 +530,14 @@ let x = if true { &temp() } else { &temp() }; # x; let x = match () { _ => &temp() }; // `match` arm expression. # x; +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. # assert_eq!(0, X.load(Relaxed)); @@ -587,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 @@ -647,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 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