-
Notifications
You must be signed in to change notification settings - Fork 550
Specify lifetime extension of pin!
and format_args!
arguments
#1980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
9adbc21
to
64644b2
Compare
64644b2
to
43c81b3
Compare
1c9c73c
to
53de4ae
Compare
src/destructors.md
Outdated
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My reading is that this rule may not be needed at all. The "extending based on expressions" section states a recursive algorithm for determining, starting on the outside and working inward, which expressions are extending. This definition then serves the following rule, which is the one that has language effect:
The operand of any extending borrow expression has its temporary scope extended.
That's the whole game, right there, I think, is deciding whether a particular borrow expression is extending.
In that context, then, the only thing that matters with respect to these built-in macros is whether expressions in their argument positions are extending expressions.
(I'll push a revision to drop this rule.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that context, then, the only thing that matters with respect to these built-in macros is whether expressions in their argument positions are extending expressions.
Unless I'm missing something, I don't think that's sufficient to describe the behavior of pin!
and format_args!
. I was struggling to write out the missing rule though, since it can't quite be expressed precisely with stable terminology: the bindings of a super let
statement in an extending block expression have their scopes extended1. In that way, super let
bindings are scoped like borrowed temporaries. The compiler implementation is spread across here and here.
This can be observed through pin!
and format_args!
:
In pin!($expr)
, the result of $expr
is moved into a super let
binding, giving it the same scope a borrowed temporary would have: if the pin!
invocation is extending, its scope is extended, and otherwise, it's dropped in the enclosing temporary scope. Since pin!
moves its argument, this can't simply be described as it borrowing it, but the Pin
doesn't own it either. To enforced pinnedness, it has to treat its argument as a value, but it also needs to scope it like it's borrowed; this (as I understand it) is why super let
is needed in Rust 20242.
format_args!
does borrow its arguments, but super let
's unique scoping can be observed there too: even if format_args!
is borrowing from long-lived places, the super let
bindings created to store the arguments have the scope a borrowed temporary would: they have extended scopes if the format_args!
invocation is extending, and otherwise they're dropped in the enclosing temporary scope.
Footnotes
-
Arguments to extending
pin!
andformat_args!
invocations being extending covers a separatesuper let
property: the initializer ofsuper let
in an extending block is extending. This is what don't apply temporary lifetime extension rules to non-extendedsuper let
rust#145838 affects. ↩ -
Likewise the reason
Pin { __pinned: &mut { $expr } }
works is it forces$expr
to evaluate to a temporary in the appropriate scope (on account of fields and block tails of extending struct and block expressions being extending). Effectively, thesuper let
-based implementation captures that property without the block tail scope being a problem in Rust 2024. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. The mental model that I think is correct is that pin!($expr)
is, in this regard, exactly like &pin mut $expr
, which is to say that the argument/operand is a place expression context, that the operand is an extending expression when the borrow is, and that the operand of such an extending borrow has its temporary scope extended.
If that's right, the cleanest way I can think of to express this is to create a concept of a "borrow macro call expression", and then to reframe the rules for extending based on expressions to work with these.
Let me know if that looks right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With regard to pin!
, I think the operand is a value expression. In the current implementation, the super let mut pinned = $value;
binds the operand by value, effectively evaluating it to a temporary1. In the old implementation it uses a block tail to force a value expression context. Functionally, pin!
has to move out of its operand, to ensure that its operand can't be moved after being pinned: https://doc.rust-lang.org/nightly/std/pin/macro.pin.html#remarks. If its operand was just borrowed, the place would be able to be moved from after the Pin
was no longer in use, violating Pin
's invariant.
format_args!
's non-format-string arguments are place expressions and implicitly borrowed, so the inclusion as a borrow macro call expression works2. I don't think it's sufficient to describe its behavior though, since the returned fmt::Arguments
also borrows from temporaries created by format_args!
, which may have shorter scopes than its operands (playground).
In both cases, the best I was able to come up with was that pin!
and format_args!
themselves create temporaries3, the scopes of which can be extended4. This is especially clear in the old implementation of pin!
: in Pin { __pointer: &mut { $value } }
, { $value }
evaluates to a temporary that may be extended because it's the operand of a borrow expression.
Footnotes
-
Technically, the initializer of a
super let
is a place expression, but then creating themut pinned
binding moves out of that place, effectively treating it like it like a value expression. ↩ -
A wording complexity though:
format_args!("{x}")
borrowsx
despite it not appearing "after" the format string. ↩ -
Currently implemented as
super let
bindings. ↩ -
Because the bindings of a
super let
in an extending block have extended scopes. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. This is I think the key:
I was struggling to write out the missing rule though, since it can't quite be expressed precisely with stable terminology...
It's just too hard to be precise here without introducing terms, so I've pushed a revision that does go ahead and introduce new terms.
Let me know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment has been minimized.
This comment has been minimized.
53de4ae
to
64f24fb
Compare
pin!
and format_args!
argumentspin!
and format_args!
arguments
3b2e90f
to
4095838
Compare
58e85ee
to
899ab5e
Compare
899ab5e
to
29cafd4
Compare
…jackh726,traviscross don't apply temporary lifetime extension rules to non-extended `super let` Reference PR: rust-lang/reference#1980 This changes the semantics for `super let` (and macros implemented in terms of it, such as `pin!`, `format_args!`, `write!`, and `println!`) as suggested by `@theemathas` in rust-lang#145784 (comment), making `super let` initializers only count as [extending expressions](https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions) when the `super let` itself is within an extending block. Since `super let` initializers aren't temporary drop scopes, their temporaries outside of inner temporary scopes are effectively always extended, even when not in extending positions; this only affects two cases as far as I can tell: - Block tail expressions in Rust 2024. This PR makes `f(pin!({ &temp() }))` drop `temp()` at the end of the block in Rust 2024, whereas previously it would live until after the call to `f` because syntactically the `temp()` was in an extending position as a result of `super let` in `pin!`'s expansion. - `super let` nested within a non-extended `super let` is no longer extended. i.e. a normal `let` is required to treat `super let`s as extending (in which case nested `super let`s will also be extending). Closes rust-lang#145784 This is a breaking change. Both static and dynamic semantics are affected. The most likely breakage is for programs to stop compiling, but it's technically possible for drop order to silently change as well (as in rust-lang#145784). Since this affects stable macros, it probably would need a crater run. Nominating for discussion alongside rust-lang#145784: `@rustbot` label +I-lang-nominated +I-libs-api-nominated Tracking issue for `super let`: rust-lang#139076
…jackh726,traviscross don't apply temporary lifetime extension rules to non-extended `super let` Reference PR: rust-lang/reference#1980 This changes the semantics for `super let` (and macros implemented in terms of it, such as `pin!`, `format_args!`, `write!`, and `println!`) as suggested by ``@theemathas`` in rust-lang#145784 (comment), making `super let` initializers only count as [extending expressions](https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions) when the `super let` itself is within an extending block. Since `super let` initializers aren't temporary drop scopes, their temporaries outside of inner temporary scopes are effectively always extended, even when not in extending positions; this only affects two cases as far as I can tell: - Block tail expressions in Rust 2024. This PR makes `f(pin!({ &temp() }))` drop `temp()` at the end of the block in Rust 2024, whereas previously it would live until after the call to `f` because syntactically the `temp()` was in an extending position as a result of `super let` in `pin!`'s expansion. - `super let` nested within a non-extended `super let` is no longer extended. i.e. a normal `let` is required to treat `super let`s as extending (in which case nested `super let`s will also be extending). Closes rust-lang#145784 This is a breaking change. Both static and dynamic semantics are affected. The most likely breakage is for programs to stop compiling, but it's technically possible for drop order to silently change as well (as in rust-lang#145784). Since this affects stable macros, it probably would need a crater run. Nominating for discussion alongside rust-lang#145784: ``@rustbot`` label +I-lang-nominated +I-libs-api-nominated Tracking issue for `super let`: rust-lang#139076
…jackh726,traviscross don't apply temporary lifetime extension rules to non-extended `super let` Reference PR: rust-lang/reference#1980 This changes the semantics for `super let` (and macros implemented in terms of it, such as `pin!`, `format_args!`, `write!`, and `println!`) as suggested by ```@theemathas``` in rust-lang#145784 (comment), making `super let` initializers only count as [extending expressions](https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions) when the `super let` itself is within an extending block. Since `super let` initializers aren't temporary drop scopes, their temporaries outside of inner temporary scopes are effectively always extended, even when not in extending positions; this only affects two cases as far as I can tell: - Block tail expressions in Rust 2024. This PR makes `f(pin!({ &temp() }))` drop `temp()` at the end of the block in Rust 2024, whereas previously it would live until after the call to `f` because syntactically the `temp()` was in an extending position as a result of `super let` in `pin!`'s expansion. - `super let` nested within a non-extended `super let` is no longer extended. i.e. a normal `let` is required to treat `super let`s as extending (in which case nested `super let`s will also be extending). Closes rust-lang#145784 This is a breaking change. Both static and dynamic semantics are affected. The most likely breakage is for programs to stop compiling, but it's technically possible for drop order to silently change as well (as in rust-lang#145784). Since this affects stable macros, it probably would need a crater run. Nominating for discussion alongside rust-lang#145784: ```@rustbot``` label +I-lang-nominated +I-libs-api-nominated Tracking issue for `super let`: rust-lang#139076
…jackh726,traviscross don't apply temporary lifetime extension rules to non-extended `super let` Reference PR: rust-lang/reference#1980 This changes the semantics for `super let` (and macros implemented in terms of it, such as `pin!`, `format_args!`, `write!`, and `println!`) as suggested by ````@theemathas```` in rust-lang#145784 (comment), making `super let` initializers only count as [extending expressions](https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions) when the `super let` itself is within an extending block. Since `super let` initializers aren't temporary drop scopes, their temporaries outside of inner temporary scopes are effectively always extended, even when not in extending positions; this only affects two cases as far as I can tell: - Block tail expressions in Rust 2024. This PR makes `f(pin!({ &temp() }))` drop `temp()` at the end of the block in Rust 2024, whereas previously it would live until after the call to `f` because syntactically the `temp()` was in an extending position as a result of `super let` in `pin!`'s expansion. - `super let` nested within a non-extended `super let` is no longer extended. i.e. a normal `let` is required to treat `super let`s as extending (in which case nested `super let`s will also be extending). Closes rust-lang#145784 This is a breaking change. Both static and dynamic semantics are affected. The most likely breakage is for programs to stop compiling, but it's technically possible for drop order to silently change as well (as in rust-lang#145784). Since this affects stable macros, it probably would need a crater run. Nominating for discussion alongside rust-lang#145784: ````@rustbot```` label +I-lang-nominated +I-libs-api-nominated Tracking issue for `super let`: rust-lang#139076
Rollup merge of #145838 - dianne:non-extending-super-let, r=jackh726,traviscross don't apply temporary lifetime extension rules to non-extended `super let` Reference PR: rust-lang/reference#1980 This changes the semantics for `super let` (and macros implemented in terms of it, such as `pin!`, `format_args!`, `write!`, and `println!`) as suggested by ````@theemathas```` in #145784 (comment), making `super let` initializers only count as [extending expressions](https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions) when the `super let` itself is within an extending block. Since `super let` initializers aren't temporary drop scopes, their temporaries outside of inner temporary scopes are effectively always extended, even when not in extending positions; this only affects two cases as far as I can tell: - Block tail expressions in Rust 2024. This PR makes `f(pin!({ &temp() }))` drop `temp()` at the end of the block in Rust 2024, whereas previously it would live until after the call to `f` because syntactically the `temp()` was in an extending position as a result of `super let` in `pin!`'s expansion. - `super let` nested within a non-extended `super let` is no longer extended. i.e. a normal `let` is required to treat `super let`s as extending (in which case nested `super let`s will also be extending). Closes #145784 This is a breaking change. Both static and dynamic semantics are affected. The most likely breakage is for programs to stop compiling, but it's technically possible for drop order to silently change as well (as in #145784). Since this affects stable macros, it probably would need a crater run. Nominating for discussion alongside #145784: ````@rustbot```` label +I-lang-nominated +I-libs-api-nominated Tracking issue for `super let`: #139076
29cafd4
to
1baced6
Compare
8c15a25
to
2404e93
Compare
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.
2404e93
to
abedccf
Compare
I've updated this to account for not having merged yet so that we can merge this. |
…traviscross don't apply temporary lifetime extension rules to non-extended `super let` Reference PR: rust-lang/reference#1980 This changes the semantics for `super let` (and macros implemented in terms of it, such as `pin!`, `format_args!`, `write!`, and `println!`) as suggested by ````@theemathas```` in rust-lang/rust#145784 (comment), making `super let` initializers only count as [extending expressions](https://doc.rust-lang.org/nightly/reference/destructors.html#extending-based-on-expressions) when the `super let` itself is within an extending block. Since `super let` initializers aren't temporary drop scopes, their temporaries outside of inner temporary scopes are effectively always extended, even when not in extending positions; this only affects two cases as far as I can tell: - Block tail expressions in Rust 2024. This PR makes `f(pin!({ &temp() }))` drop `temp()` at the end of the block in Rust 2024, whereas previously it would live until after the call to `f` because syntactically the `temp()` was in an extending position as a result of `super let` in `pin!`'s expansion. - `super let` nested within a non-extended `super let` is no longer extended. i.e. a normal `let` is required to treat `super let`s as extending (in which case nested `super let`s will also be extending). Closes rust-lang/rust#145784 This is a breaking change. Both static and dynamic semantics are affected. The most likely breakage is for programs to stop compiling, but it's technically possible for drop order to silently change as well (as in rust-lang/rust#145784). Since this affects stable macros, it probably would need a crater run. Nominating for discussion alongside rust-lang/rust#145784: ````@rustbot```` label +I-lang-nominated +I-libs-api-nominated Tracking issue for `super let`: rust-lang/rust#139076
Update books ## rust-lang/book 1 commits in 3e9dc46aa563ca0c53ec826c41b05f10c5915925..33f1af40cc44dde7e3e892f7a508e6f427d2cbc6 2025-09-15 16:10:14 UTC to 2025-09-15 16:10:14 UTC - Release trpl 0.3 (rust-lang/book#4505) ## rust-lang/reference 9 commits in b3ce60628c6f55ab8ff3dba9f3d20203df1c0dee..cc7247d8dfaef4c39000bb12c55c32ba5b5ba976 2025-09-20 10:26:26 UTC to 2025-09-08 18:07:29 UTC - Document temporary scoping for destructuring assignments (rust-lang/reference#1992) - Specify lifetime extension of `pin!` and `format_args!` arguments (rust-lang/reference#1980) - update for more ABIs supporting c-variadics (rust-lang/reference#1936) - Fix incorrect span tag (rust-lang/reference#1995) - Remove strike attribute (rust-lang/reference#1997) - Specify the target limits for target-specific ABIs (rust-lang/reference#2000) - Remove tuple index carve out (rust-lang/reference#1966) - Enable folding of chapter listing in navigation sidebar (rust-lang/reference#1988) - Add support to grammar for single line comments (rust-lang/reference#1993) ## rust-lang/rust-by-example 1 commits in dd26bc8e726dc2e73534c8972d4dccd1bed7495f..2c9b490d70e535cf166bf17feba59e594579843f 2025-09-18 22:28:52 UTC to 2025-09-18 22:28:52 UTC - Update unit testing output for additional test (rust-lang/rust-by-example#1958)
Update books ## rust-lang/book 1 commits in 3e9dc46aa563ca0c53ec826c41b05f10c5915925..33f1af40cc44dde7e3e892f7a508e6f427d2cbc6 2025-09-15 16:10:14 UTC to 2025-09-15 16:10:14 UTC - Release trpl 0.3 (rust-lang/book#4505) ## rust-lang/reference 9 commits in b3ce60628c6f55ab8ff3dba9f3d20203df1c0dee..cc7247d8dfaef4c39000bb12c55c32ba5b5ba976 2025-09-20 10:26:26 UTC to 2025-09-08 18:07:29 UTC - Document temporary scoping for destructuring assignments (rust-lang/reference#1992) - Specify lifetime extension of `pin!` and `format_args!` arguments (rust-lang/reference#1980) - update for more ABIs supporting c-variadics (rust-lang/reference#1936) - Fix incorrect span tag (rust-lang/reference#1995) - Remove strike attribute (rust-lang/reference#1997) - Specify the target limits for target-specific ABIs (rust-lang/reference#2000) - Remove tuple index carve out (rust-lang/reference#1966) - Enable folding of chapter listing in navigation sidebar (rust-lang/reference#1988) - Add support to grammar for single line comments (rust-lang/reference#1993) ## rust-lang/rust-by-example 1 commits in dd26bc8e726dc2e73534c8972d4dccd1bed7495f..2c9b490d70e535cf166bf17feba59e594579843f 2025-09-18 22:28:52 UTC to 2025-09-18 22:28:52 UTC - Update unit testing output for additional test (rust-lang/rust-by-example#1958)
Reference PR for rust-lang/rust#145838, given the
format_args!
change in rust-lang/rust#145882. cc @m-ou-seBased on #1979; the first commit is the commit from that PR.