Skip to content

Conversation

dianne
Copy link
Contributor

@dianne dianne commented Aug 25, 2025

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 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 lets as extending (in which case nested super lets 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

@rustbot
Copy link
Collaborator

rustbot commented Aug 25, 2025

r? @jackh726

rustbot has assigned @jackh726.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. I-lang-nominated Nominated for discussion during a lang team meeting. I-libs-api-nominated Nominated for discussion during a libs-api team meeting. labels Aug 25, 2025
@rust-log-analyzer

This comment has been minimized.

@dianne dianne force-pushed the non-extending-super-let branch from 0542d4f to a35548f Compare August 25, 2025 07:51
@dianne
Copy link
Contributor Author

dianne commented Aug 25, 2025

Copied from #145784 (comment), since I think this is a notable caveat of this PR and worth considering before approving it:

This comes with a bit of a gotcha in terms of temporary lifetimes: it might be strange that the temp() would live longer in

non_extending({ let x = { &temp() }; f(x) }); // ok

than in

non_extending({ super let x = { &temp() }; f(x) }); // error

Though the case for if let is similar: its scrutinee isn't a temporary scope and it doesn't have lifetime extension rules that can make block tail expressions' temporaries live longer.

@dianne
Copy link
Contributor Author

dianne commented Aug 25, 2025

Also copied since it motivates this PR: I think something like this may be necessary for the identity

&EXPR === { super let x = &EXPR; x }

to hold in both extending and non-extending contexts without changing how block tail expression scopes work. Substituting, e.g. { &temp() } in for EXPR, the identity only currently holds in extending contexts in Rust 2024. In non-extending contexts,

&{ &temp() }

drops temp() when leaving the block, but

{ super let x = &{ &temp() }; x }

would extend it to outlive x. This PR shortens the lifetime of temp() such that it's dropped at the end of the block in both cases. I haven't done any rigorous proof or extensive testing that this PR together with #145342 makes the identity always hold, however.

@jieyouxu jieyouxu added the T-lang Relevant to the language team label Aug 25, 2025
@traviscross traviscross added the P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang label Aug 25, 2025
@traviscross
Copy link
Contributor

traviscross commented Aug 25, 2025

To confirm, with this PR, does this behavior hold (in Rust 2024)?:

fn f<T>(_: LogDrop<'_>, x: T) -> T { x }

// These two should be the same.
assert_drop_order(1..=3, |e| {
    let _v = f(e.log(2), &{ &raw const *&e.log(1) });
    drop(e.log(3));
});
assert_drop_order(1..=3, |e| {
    let _v = f(e.log(2), {
        super let v = &{ &raw const *&e.log(1) };
        v
    });
    drop(e.log(3));
});
// These two should be the same.
assert_drop_order(1..=3, |e| {
    let _v = f(e.log(1), &&raw const *&e.log(2));
    drop(e.log(3));
});
assert_drop_order(1..=3, |e| {
    let _v = f(e.log(1), {
        super let v = &&raw const *&e.log(2);
        v
    });
    drop(e.log(3));
});
// These two should be the same.
assert_drop_order(1..=2, |e| {
    let _v = &{ &raw const *&e.log(2) };
    drop(e.log(1));
});
assert_drop_order(1..=2, |e| {
    let _v = {
        super let v = &{ &raw const *&e.log(2) };
        v
    };
    drop(e.log(1));
});

Playground link

(If any of these are missing, please add them as tests.)

@theemathas
Copy link
Contributor

theemathas commented Aug 25, 2025

super let nested within a non-extended super let is no longer extended

Does this PR affect any edition 2021 code?

@dianne dianne force-pushed the non-extending-super-let branch from a35548f to f0c43cf Compare August 25, 2025 12:05
@dianne
Copy link
Contributor Author

dianne commented Aug 25, 2025

To confirm, with this PR, does this behavior hold (in Rust 2024)?:

Those all hold under this PR, yes. I've added them all as tests (with some additional versioning to account for the Edition-dependent drop order in the first one); thanks!

super let nested within a non-extended super let is no longer extended

Does this PR affect any edition 2021 code?

Not that I'm aware of. That detail matters in Edition 2024, since the nested super let could have an extending borrow operator within a block tail expression in its initializer, which would previously extend the borrowed temporary's lifetime to outlive the outer super let's binding. In Edition 2021, that temporary outlives the outer super let's binding regardless, since block tail expressions (and super let initializers) aren't temporary scopes.

@rust-log-analyzer

This comment has been minimized.

@dianne dianne force-pushed the non-extending-super-let branch from f0c43cf to 387cfa5 Compare August 25, 2025 12:23
Comment on lines 131 to 132
// We have extending borrow expressions within the initializer
// expression.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// We have extending borrow expressions within the initializer
// expression.
// We have extending borrow expressions within a non-extending
// expression within the initializer expression.

(Revising my earlier text here.)

@traviscross traviscross added needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. S-waiting-on-documentation Status: Waiting on approved PRs to documentation before merging and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 25, 2025
@traviscross
Copy link
Contributor

This is correct, I believe. Let's propose to do it.

@rfcbot fcp merge

@dianne, let's document this in the Reference.

cc @rust-lang/lang-docs

@rfcbot
Copy link

rfcbot commented Aug 25, 2025

Team member @traviscross has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Aug 25, 2025
@traviscross
Copy link
Contributor

cc @m-ou-se @rust-lang/libs-api

@dianne dianne force-pushed the non-extending-super-let branch from 387cfa5 to 23caea2 Compare August 26, 2025 00:58
dianne added a commit to dianne/updownserv-future-rustc-compat that referenced this pull request Sep 9, 2025
dianne added a commit to dianne/updownserv-future-rustc-compat that referenced this pull request Sep 9, 2025
@traviscross
Copy link
Contributor

Not to hurry anyone, exactly -- we're all busy -- but looking at the calendar, we only have a couple more days for this to land before we'll need to ask for a beta backport for Rust 1.91.

Also, it would be convenient if this change could land at the same time as #145882, as the Reference update in rust-lang/reference#1980 assumes both.

Cc @petrochenkov (as reviewer on #145882).

Copy link
Member

@jackh726 jackh726 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change to making the match itself return a bool is nicer. Tests seem pretty well written.

View changes since this review

@jackh726
Copy link
Member

r=me for this, whether you want to land this independently or alongside #145882.

@traviscross
Copy link
Contributor

Thanks for the review. Let's go ahead and get this in so we can start with the beta nomination.

@bors r=jackh726,traviscross rollup

@bors
Copy link
Collaborator

bors commented Sep 16, 2025

📌 Commit d5b5a4a has been approved by jackh726,traviscross

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 16, 2025
jhpratt added a commit to jhpratt/rust that referenced this pull request Sep 17, 2025
…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
bors added a commit that referenced this pull request Sep 17, 2025
Rollup of 9 pull requests

Successful merges:

 - #144871 (Stabilize `btree_entry_insert` feature)
 - #145181 (remove FIXME block from `has_significant_drop`, it never encounters inference variables)
 - #145838 (don't apply temporary lifetime extension rules to non-extended `super let`)
 - #146259 (Suggest removing Box::new instead of unboxing it)
 - #146410 (Iterator repeat: no infinite loop for `last` and `count`)
 - #146460 (Add tidy readme)
 - #146581 (Detect attempt to use var-args in closure)
 - #146588 (tests/run-make: Update list of statically linked musl targets)
 - #146647 (Move `#[rustc_coherence_is_core]` to the `crate_level` file)

r? `@ghost`
`@rustbot` modify labels: rollup
Zalathar added a commit to Zalathar/rust that referenced this pull request Sep 17, 2025
…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
Zalathar added a commit to Zalathar/rust that referenced this pull request Sep 17, 2025
…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
bors added a commit that referenced this pull request Sep 17, 2025
Rollup of 14 pull requests

Successful merges:

 - #142807 (libtest: expose --fail-fast as an unstable command-line option)
 - #144871 (Stabilize `btree_entry_insert` feature)
 - #145071 (Update the minimum external LLVM to 20)
 - #145181 (remove FIXME block from `has_significant_drop`, it never encounters inference variables)
 - #145660 (initial implementation of the darwin_objc unstable feature)
 - #145838 (don't apply temporary lifetime extension rules to non-extended `super let`)
 - #146259 (Suggest removing Box::new instead of unboxing it)
 - #146410 (Iterator repeat: no infinite loop for `last` and `count`)
 - #146460 (Add tidy readme)
 - #146552 (StateTransform: Do not renumber resume local.)
 - #146564 (Remove Rvalue::Len again.)
 - #146581 (Detect attempt to use var-args in closure)
 - #146588 (tests/run-make: Update list of statically linked musl targets)
 - #146631 (cg_llvm: Replace some DIBuilder wrappers with LLVM-C API bindings (part 3))

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit c2e8264 into rust-lang:master Sep 17, 2025
10 checks passed
@rustbot rustbot added this to the 1.92.0 milestone Sep 17, 2025
rust-timer added a commit that referenced this pull request Sep 17, 2025
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
@theemathas theemathas added the beta-nominated Nominated for backporting to the compiler in the beta channel. label Sep 17, 2025
@apiraino
Copy link
Contributor

Beta backport declined as per compiler team on Zulip and discussion here.

@rustbot label -beta-nominated

@rustbot rustbot removed the beta-nominated Nominated for backporting to the compiler in the beta channel. label Sep 18, 2025
@kornelski
Copy link
Contributor

This is a breaking change!

Please don't rush it to be insta-stable without some migration period or a mitigation (like future-compat warnings, or some opt-in, or at least keep it nighty-only for a longer period).

Formatting strings don't support conditions, so use of a nested if cond { &format!("") } else { "default" } is justifiable, especially that it avoids another allocation when the else is a non-empty string. I've used this myself, since if { format_args!() } wasn't possible until recently (1.89.0).

I agree with the overall change, since the lifetime extension was already inconsistent, but I'm worried this may suddenly break legitimate code, and Rust isn't supposed to be doing that.

@traviscross
Copy link
Contributor

On lang, we are and were aware this is a breaking change. That's why we did a crater run and ensured PRs were submitted to the affected projects.

That's also why we'd prefer to get this change released sooner rather than later. The longer that the other behavior is stable, the more people will fall into writing the thing that you mention even though we don't (currently) intend for that to work.

What went out with format_args! in Rust 1.89 amounted to an accidental stabilization. When we do those, we try to take it back, if we can. Even if we might later want to support something like this in the future, such as via @dianne's proposal in #146098, our practice has been to first fix the bug when possible. We then do the design work on the feature and later land it intentionally.

When deciding whether we can take back an accidental stabilization directly, we consider the degree and character of the breakage, among other factors. Here, we judged that as acceptable given the benefit and given that we're taking this back relatively quickly after releasing this bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team to-announce Announce this issue on triage meeting
Projects
None yet
Development

Successfully merging this pull request may close these issues.

pin!() changed temporary lifetime extension behavior in version 1.88.0 with edition 2024 tail expression temporary scopes