Skip to content

Stabilize let chains in the 2024 edition #132833

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

est31
Copy link
Member

@est31 est31 commented Nov 10, 2024

Stabilization report

This proposes the stabilization of let_chains (tracking issue, RFC 2497) in the 2024 edition of Rust.

What is being stabilized

The ability to &&-chain let statements inside if and while is being stabilized, allowing intermixture with boolean expressions. The patterns inside the let sub-expressions can be irrefutable or refutable.

struct FnCall<'a> {
    fn_name: &'a str,
    args: Vec<i32>,
}

fn is_legal_ident(s: &str) -> bool {
    s.chars()
        .all(|c| ('a'..='z').contains(&c) || ('A'..='Z').contains(&c))
}

impl<'a> FnCall<'a> {
    fn parse(s: &'a str) -> Option<Self> {
        if let Some((fn_name, after_name)) = s.split_once("(")
            && !fn_name.is_empty()
            && is_legal_ident(fn_name)
            && let Some((args_str, "")) = after_name.rsplit_once(")")
        {
            let args = args_str
                .split(',')
                .map(|arg| arg.parse())
                .collect::<Result<Vec<_>, _>>();
            args.ok().map(|args| FnCall { fn_name, args })
        } else {
            None
        }
    }
    fn exec(&self) -> Option<i32> {
        let iter = self.args.iter().copied();
        match self.fn_name {
            "sum" => Some(iter.sum()),
            "max" => iter.max(),
            "min" => iter.min(),
            _ => None,
        }
    }
}

fn main() {
    println!("{:?}", FnCall::parse("sum(1,2,3)").unwrap().exec());
    println!("{:?}", FnCall::parse("max(4,5)").unwrap().exec());
}

The feature will only be stabilized for the 2024 edition and future editions. Users of past editions will get an error with a hint to update the edition.

closes #53667

Why 2024 edition?

Rust generally tries to ship new features to all editions. So even the oldest editions receive the newest features. However, sometimes a feature requires a breaking change so much that offering the feature without the breaking change makes no sense. This occurs rarely, but has happened in the 2018 edition already with async and await syntax. It required an edition boundary in order for async/await to become keywords, and the entire feature foots on those keywords.

In the instance of let chains, the issue is the drop order of if let chains. If we want if let chains to be compatible with if let, drop order makes it hard for us to generate correct MIR. It would be strange to have different behaviour for if let ... {} and if true && let ... {}. So it's better to stay consistent with if let.

In edition 2024, drop order changes have been introduced to make if let temporaries be lived more shortly. These changes also affected if let chains. These changes make sense even if you don't take the if let chains MIR generation problem into account. But if we want to use them as the solution to the MIR generation problem, we need to restrict let chains to edition 2024 and beyond: for let chains, it's not just a change towards more sensible behaviour, but one required for correct function.

Introduction considerations

As edition 2024 is very new, this stabilization PR only makes it possible to use let chains on 2024 without that feature gate, it doesn't mark that feature gate as stable/removed. I would propose to continue offering the let_chains feature (behind a feature gate) for a limited time (maybe 3 months after stabilization?) on older editions to allow nightly users to adopt edition 2024 at their own pace. After that, the feature gate shall be marked as stabilized, not removed, and replaced by an error on editions 2021 and below.

Implementation history

Adoption history

In the compiler

Outside of the compiler

Tests

Intentional restrictions

partially-macro-expanded.rs, macro-expanded.rs: it is possible to use macros to expand to both the pattern and the expression inside a let chain, but not to the entire let pat = expr operand.
parens.rs: if (let pat = expr) is not allowed in chains
ensure-that-let-else-does-not-interact-with-let-chains.rs: let...else doesn't support chaining.

Overlap with match guards

move-guard-if-let-chain.rs: test for the use moved value error working well in match guards. could maybe be extended with let chains that have more than one let
shadowing.rs: shadowing in if let guards works as expected
ast-validate-guards.rs: let chains in match guards require the match guards feature gate

Simple cases from the early days

PR #88642 has added some tests with very simple usages of let else, mostly as regression tests to early bugs.

then-else-blocks.rs
ast-lowering-does-not-wrap-let-chains.rs
issue-90722.rs
issue-92145.rs

Drop order/MIR scoping tests

issue-100276.rs: let expressions on RHS aren't terminating scopes
drop_order.rs: exhaustive temporary drop order test for various Rust constructs, including let chains
scope.rs: match guard scoping test
drop-scope.rs: another match guard scoping test, ensuring that temporaries in if-let guards live for the arm
drop_order_if_let_rescope.rs: if let rescoping on edition 2024, including chains
mir_let_chains_drop_order.rs: comprehensive drop order test for let chains, distinguishes editions 2021 and 2024.
issue-99938.rs, issue-99852.rs both bad MIR ICEs fixed by #102394

Linting

irrefutable-lets.rs: trailing and leading irrefutable let patterns get linted for, others don't. The lint is turned off for else if.
issue-121070-let-range.rs: regression test for false positive of the unused parens lint, precedence requires the ()s here

Parser: intentional restrictions

disallowed-positions.rs: let in expression context is rejected everywhere except at the top level
invalid-let-in-a-valid-let-context.rs: nested let is not allowed (let's are no legal expressions just because they are allowed in if and while).

Parser: recovery

issue-103381.rs: Graceful recovery of incorrect chaining of if and if let
semi-in-let-chain.rs: Ensure that stray ;s in let chains give nice errors (if_chain! users might be accustomed to ;s)
deli-ident-issue-1.rs, brace-in-let-chain.rs: Ensure that stray unclosed {s in let chains give nice errors and hints

Misc

conflicting_bindings.rs: the conflicting bindings check also works in let chains. Personally, I'd extend it to chains with multiple let's as well.
let-chains-attr.rs: attributes work on let chains

Tangential tests with #![feature(let_chains)]

if-let.rs: MC/DC coverage tests for let chains
logical_or_in_conditional.rs: not really about let chains, more about dropping/scoping behaviour of ||
stringify.rs: exhaustive test of the stringify macro
expanded-interpolation.rs, expanded-exhaustive.rs: Exhaustive test of -Zunpretty
diverges-not.rs: Never type, mostly tangential to let chains

Possible future work

  • There is proposals to allow if let Pat(bindings) = expr {} to be written as if expr is Pat(bindings) {} (RFC 3573). if let chains are a natural extension of the already existing if let syntax, and I'd argue orthogonal towards is syntax.
  • One could have similar chaining inside let ... else statements. There is no proposed RFC for this however, nor is it implemented on nightly.
  • Match guards have the if keyword as well, but on stable Rust, they don't support let. The functionality is available via an unstable feature (if_let_guard tracking issue). Stabilization of let chains affects this feature in so far as match guards containing let chains now only need the if_let_guard feature gate be present instead of also the let_chains feature (NOTE: this PR doesn't implement this simplification, it's left for future work).

Open questions / blockers

@est31 est31 added the F-let_chains `#![feature(let_chains)]` label Nov 10, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Nov 10, 2024
@jieyouxu jieyouxu added T-lang Relevant to the language team, which will review and decide on the PR/issue. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC A-edition-2024 Area: The 2024 edition and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Nov 10, 2024
@traviscross
Copy link
Contributor

traviscross commented Nov 10, 2024

Process-wise, we normally do the FCP on the stabilization PR, and I think that'd make the most sense here. Let us know if you need us on lang to weigh in on anything specifically, e.g. any of the open questions, to make such a stabilization PR possible.

cc @rust-lang/lang for visibility.

And cc @rust-lang/style given the open item on the style guide.

@rustbot labels +I-style-nominated

Putting on the edition hat, since this would be an edition item in some sense, we should talk about this in our next edition call.

@rustbot labels +I-edition-nominated

@rustbot rustbot added I-style-nominated Nominated for discussion during a style team meeting. I-edition-nominated Nominated for discussion during an edition team meeting. labels Nov 10, 2024
@rustbot
Copy link
Collaborator

rustbot commented Nov 10, 2024

r? @fee1-dead

rustbot has assigned @fee1-dead.
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 the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 10, 2024
@est31 est31 removed the C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC label Nov 10, 2024
@est31
Copy link
Member Author

est31 commented Nov 10, 2024

Process-wise, we normally do the FCP on the stabilization PR, and I think that'd make the most sense here.

@traviscross understood, I've converted it to a pull request using the gh cli. Thanks for cc'ing the relevant teams.

@traviscross
Copy link
Contributor

Beautiful, thanks. Let's nominate this for lang discussion too then.

@rustbot labels +I-lang-nominated

@rustbot rustbot added the I-lang-nominated Nominated for discussion during a lang team meeting. label Nov 10, 2024
@rust-log-analyzer

This comment has been minimized.

@fee1-dead fee1-dead self-assigned this Nov 11, 2024
@compiler-errors compiler-errors added the S-waiting-on-fcp Status: PR is in FCP and is awaiting for FCP to complete. label Nov 11, 2024
@compiler-errors compiler-errors removed their assignment Nov 11, 2024
@fee1-dead fee1-dead added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 12, 2024
@traviscross
Copy link
Contributor

@rustbot labels -I-edition-nominated

We talked this one through in the edition call. Since there's no breakage or migration here, we're happy for this one to land in Rust 2024 either ahead of or after the release of Rust 2024 itself as long as there is an edition guide chapter for it.

@rustbot rustbot removed the I-edition-nominated Nominated for discussion during an edition team meeting. label Nov 13, 2024
@est31 est31 force-pushed the stabilize_let_chains branch from aa24aa5 to d57626e Compare November 13, 2024 09:55
@est31
Copy link
Member Author

est31 commented Apr 7, 2025

Thanks @calebcartwright for filing the style guide pull request and guiding the PR towards merging. It's really great news that this feature is (again) doing the last steps towards stabilization.

I have rebased the pull request. @fee1-dead if you want you can already take a look now.

@camsteffen

I've been trying to follow along and I don't know that it was ever clearly communicated to the community that the style team had made a final decision, and that the next step was for someone to open a PR. Maybe I missed something?

There has been a msg in the zulip thread here in January, so I've been aware of that since that point of time. That thread had been in the "Open questions / blockers" section of the pull request description since a long time.

In February I've listed the open blockers and also asked for someone to file a PR. I should probably have responded to your msg a week later on March 7 about formatting, probably my msg above hasn't been clear enough.

In the end we were one month hard blocked on formatting, which isn't that much in the grand scheme of things (note that this PR is open for 6 months at this point). People have been busy with the edition 2024 release earlier this year which had priority also for the style team. Many of us are free time contributors who don't have this as a day job, me included.

@rust-log-analyzer

This comment has been minimized.

@est31 est31 force-pushed the stabilize_let_chains branch from 77c404c to c373f03 Compare April 7, 2025 16:57
@traviscross traviscross added I-lang-radar Items that are on lang's radar and will need eventual work or consideration. and removed I-style-nominated Nominated for discussion during a style team meeting. I-lang-nominated Nominated for discussion during a lang team meeting. labels Apr 9, 2025
@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Apr 17, 2025
@rfcbot
Copy link
Collaborator

rfcbot commented Apr 17, 2025

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@fmease fmease added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. S-waiting-on-fcp Status: PR is in FCP and is awaiting for FCP to complete. labels Apr 17, 2025
Comment on lines 2708 to 2723
/// The specified `edition` should be that of the whole `if` or `while` construct: the same
/// span that we later decide the drop behaviour on (editions ..=2021 vs 2024..)
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
/// The specified `edition` should be that of the whole `if` or `while` construct: the same
/// span that we later decide the drop behaviour on (editions ..=2021 vs 2024..)
/// The specified `edition` should be that of the whole `if` or `while`
/// construct, i.e. the same span we use to later decide whether the drop
/// behavior should be that of `..=2021` or that of `2024..`, as this is
/// important for let chains.

(Mostly wanted to add the trailing period, but then reworded a bit for hopefully better clarity.)

Copy link
Member Author

Choose a reason for hiding this comment

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

reworded

Comment on lines 2738 to 2739
// Scoping code checks the top level edition of the `if`: let's match it here.
// Also check all editions in between, just to make sure.
Copy link
Contributor

@traviscross traviscross Apr 17, 2025

Choose a reason for hiding this comment

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

Suggested change
// Scoping code checks the top level edition of the `if`: let's match it here.
// Also check all editions in between, just to make sure.
// Scoping code checks the top level edition of the `if`; let's
// match it here. Also check all editions in between, just to
// make sure.

(Replacing colon with semicolon as is appropriate here.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Put a ; there.

ungate_let_exprs(this, lhs);
}
ExprKind::Let(..) => {
this.psess.gated_spans.ungate_last(sym::let_chains, expr.span)
Copy link
Contributor

@traviscross traviscross Apr 17, 2025

Choose a reason for hiding this comment

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

The docs for this function say:

Using this is discouraged unless you have a really good reason to.

Perhaps we should add a comment to document our good reason.

Also, since it seems unlikely to me that we'll ever stabilize let chains in Rust ..=2021 -- that is, unless we ever decide to make a breaking change to earlier editions to align the drop order rules -- I wonder if it wouldn't be better to just pull let chains out of older editions entirely and remove use of the feature gate (especially as this commit also reduces test coverage for this feature in older editions).

Alternatively, we could put let chains for older editions under a different feature gate so that we can mark let_chains itself as stable -- it'd be bit odd still having the let_chains gate marked as unstable after this PR in rustc_feature.

Copy link
Member Author

Choose a reason for hiding this comment

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

I wonder if it wouldn't be better to just pull let chains out of older editions entirely and remove use of the feature gate.

Back when I wrote the PR, the compiler has still been on edition 2021, and 2024 hasn't been stable yet. So it was a hard criterion to support 2021 in some way, too.

Now it's months later, and I guess it's feasible also to nightly users that we can immediately drop support for edition 2021 and below. But I'd still prefer to do that in a followup to this PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

Perhaps we should add a comment to document our good reason.

I wanted to be careful, especially when seeing PRs like #95008 and #98633. But I think with #115677, we probably have good recognition of when some let is in a disallowed location. So I've made a change to remove ungate_last usages and to instead not gate in the first place.

// Remove the last feature gating of a `let` expression since it's stable.
self.psess.gated_spans.ungate_last(sym::let_chains, cond.span);
if has_let_expr(&cond) {
// Let chains are allowed in match guards, but only there
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
// Let chains are allowed in match guards, but only there
// Let chains are allowed in match guards, but only there.

Also, perhaps you could elaborate on this comment. It's currently not very clarifying for me.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed the entire comment. the point was that match guards support let chains, but if you have an if inside a block inside a match guard, let chains aren't supported again.

@est31 est31 force-pushed the stabilize_let_chains branch from c373f03 to 767073c Compare April 18, 2025 13:42
@rust-log-analyzer

This comment has been minimized.

est31 added 4 commits April 18, 2025 15:57
The edition gate is a bit stricter than the drop behaviour,
which is fine. The cases we want to avoid are the opposite:
not gated but 2021 drop behaviour.
@est31 est31 force-pushed the stabilize_let_chains branch from 767073c to 07dccf5 Compare April 18, 2025 13:58
Also, stabilize while let chains on editions 2021 and before.
@est31 est31 force-pushed the stabilize_let_chains branch from 07dccf5 to b4bd8b5 Compare April 18, 2025 14:05
@est31 est31 requested a review from traviscross April 18, 2025 14:14
Copy link
Member

@fee1-dead fee1-dead left a comment

Choose a reason for hiding this comment

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

Am I also understanding this correctly that this PR will also stablize the following on all editions?

fn main() {
    let x: Option<Option<i32>> = None;
    while let Some(x) = x && let Some(_) = x {}
}

Has there been any discussion to stabilize that? My understanding is that only 2024 was discussed and FCP'd, but maybe I missed something. If only 2024 was discussed then we probably can't stabilize any form of let chains with edition < 2024.

Comment on lines +4039 to +4044
/// Whether let chains are allowed on all editions, or it's edition dependent.
/// In case of edition dependence, specify the currently present edition.
pub enum LetChainsPolicy {
AlwaysAllowed,
Edition(Edition),
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Whether let chains are allowed on all editions, or it's edition dependent.
/// In case of edition dependence, specify the currently present edition.
pub enum LetChainsPolicy {
AlwaysAllowed,
Edition(Edition),
}
/// Whether let chains are allowed on all editions, or it's edition dependent.
/// In case of edition dependence, specify the currently present edition.
pub enum LetChainsContext {
AlwaysAllowed,
EditionDependent { current_edition: Edition },
}

When you write it as LetChainsPolicy it feels like the edition that you are passing in is some sort of condition that must be passed, as compared to the current edition. I believe this change would help clarify things better.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-edition-2024 Area: The 2024 edition disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. F-let_chains `#![feature(let_chains)]` 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. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-lang Relevant to the language team, which will review and decide on the PR/issue. to-announce Announce this issue on triage meeting
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tracking issue for eRFC 2497, "if- and while-let-chains, take 2"