Skip to content
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

Stabilize let chains in the 2024 edition #132833

Open
wants to merge 3 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, and the compiler doesn't use it yet, most usages in the compiler need to still use #![feature(let_chains)]: 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.

Open questions / blockers

  • bad recovery if you don't put a let (I don't think this is a blocker): #117977
  • An instance where a temporary lives shorter than with nested ifs, breaking compilation: #103476. Personally I don't think this is a blocker either, as it's an edge case. Edit: turns out to not reproduce in edition 2025 any more, due to let rescoping. regression test added in Let chains tests #133093
  • One should probably extend the tests for move-guard-if-let-chain.rs and conflicting_bindings.rs to have chains with multiple let's: done in 133093
  • Parsing rejection tests: addressed by Additional tests to ensure let is rejected during parsing #132828
  • Formatting: nightly rustfmt can format let chains since already more than a year ago. However, the PR to add the respective policy document has not been merged.
  • Non-terminals, e.g. expr, have diverged between parser and macro matcher #86730 explicitly mentions let_else. I think we can live with let pat = expr not evaluating as expr for macro_rules macros, especially given that let pat = expr is not a legal expression anywhere except inside if and while.
  • Documentation in the reference. There has been an original reference PR during the first attempt to stabilize let chains, but it got reverted. One would need to revive it.
  • Add chapter to the Rust 2024 edition guide: Document if/while let chains edition-guide#337
  • Resolve open questions on desired drop order.

@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
@traviscross
Copy link
Contributor

@rfcbot concern resolve-open-items

@tmandry
Copy link
Member

tmandry commented Nov 13, 2024

Thanks for putting this together, @est31!

@rfcbot reviewed

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.

Compiler changes as it stands LGTM, so r=me if no additional reviews needed from me.

@fee1-dead fee1-dead removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 15, 2024
@nikomatsakis
Copy link
Contributor

@rfcbot reviewed

@nikomatsakis
Copy link
Contributor

@rfcbot concern temporary-lifetimes

Reading over the stabilization report I see this:

@est31 I am nervous about this, the precise example in the issue may be an edge case, but if we think the temporary lifetimes will need to be changed, it seems like we should resolve that first. What am I missing here?

@nikomatsakis
Copy link
Contributor

@rfcbot concern reference-pr

The issue notes the need for a reference PR. Has that been resolved? If so, I didn't see it.

@slanterns
Copy link
Contributor

Yeah it's included in the resolve-open-items concern.

@est31
Copy link
Member Author

est31 commented Nov 16, 2024

@nikomatsakis fair points. I've had another look at #103476, and it seems to not reproduce on edition 2024 any more, I suppose thanks to the let rescoping work. So I'd say we can mark it as addressed? See the latest comment in the issue thread.

@est31 est31 mentioned this pull request Nov 16, 2024
@nikomatsakis
Copy link
Contributor

I've had another look at #103476, and it seems to not reproduce on edition 2024 any more, I suppose thanks to the let rescoping work.

Ah interesting I wondered if that would be the case. Can you point me at what tests we have (assuming we have some) of if let temporary scope behavior?

@est31
Copy link
Member Author

est31 commented Nov 16, 2024

@nikomatsakis sure! The tests are listed in the stabilization report (you need to expand), under the "Drop order/MIR scoping tests" header.

In addition, there is let chain tests added by #133093.

jhpratt added a commit to jhpratt/rust that referenced this pull request Nov 17, 2024
Let chains tests

Filing this as this marks off two of the open issues in rust-lang#132833:

* extending the tests for `move-guard-if-let-chain.rs` and `conflicting_bindings.rs` to have chains with multiple let's (one implementation could for example search for the first `let` and then terminate).
* An instance where a temporary lives shorter than with nested ifs, breaking compilation: rust-lang#103476. This was fixed in the end by the if let rescoping work.

Closes rust-lang#103476
jieyouxu added a commit to jieyouxu/rust that referenced this pull request Nov 17, 2024
Let chains tests

Filing this as this marks off two of the open issues in rust-lang#132833:

* extending the tests for `move-guard-if-let-chain.rs` and `conflicting_bindings.rs` to have chains with multiple let's (one implementation could for example search for the first `let` and then terminate).
* An instance where a temporary lives shorter than with nested ifs, breaking compilation: rust-lang#103476. This was fixed in the end by the if let rescoping work.

Closes rust-lang#103476
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Nov 17, 2024
Rollup merge of rust-lang#133093 - est31:let_chains_tests, r=traviscross

Let chains tests

Filing this as this marks off two of the open issues in rust-lang#132833:

* extending the tests for `move-guard-if-let-chain.rs` and `conflicting_bindings.rs` to have chains with multiple let's (one implementation could for example search for the first `let` and then terminate).
* An instance where a temporary lives shorter than with nested ifs, breaking compilation: rust-lang#103476. This was fixed in the end by the if let rescoping work.

Closes rust-lang#103476
@bors
Copy link
Contributor

bors commented Nov 23, 2024

☔ The latest upstream changes (presumably #133349) made this pull request unmergeable. Please resolve the merge conflicts.

@bors bors added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Nov 23, 2024
@est31 est31 force-pushed the stabilize_let_chains branch from d57626e to b06c02e Compare November 24, 2024 11:30
@nikomatsakis
Copy link
Contributor

@rfcbot resolve reference-pr

@nikomatsakis
Copy link
Contributor

@rfcbot resolve temporary-lifetimes

I will review.

@bors
Copy link
Contributor

bors commented Nov 29, 2024

☔ The latest upstream changes (presumably #133634) made this pull request unmergeable. Please resolve the merge conflicts.

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 b06c02e to 64728ca Compare November 30, 2024 13:16
@GoldsteinE
Copy link
Contributor

Am I correct to assume that this won’t get into 2024 edition since 1.85 is already in beta?

@compiler-errors
Copy link
Member

I think it has been noted a few times in different places that this PR proposes to stabilize let chain only in the 2024 edition, not that it's going to land at the same time as the edition. It will happen after the release of the edition.

@GoldsteinE
Copy link
Contributor

Thanks for the clarification!

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)]` I-lang-nominated Nominated for discussion during a lang team meeting. I-style-nominated Nominated for discussion during a style team meeting. proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. 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. T-lang Relevant to the language team, which will review and decide on the PR/issue.
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"