Skip to content

Conversation

Kivooeo
Copy link
Member

@Kivooeo Kivooeo commented May 20, 2025

Summary

This proposes the stabilization of if let guards (tracking issue: #51114, RFC: rust-lang/rfcs#2294). This feature allows if let expressions to be used directly within match arm guards, enabling conditional pattern matching within guard clauses.

What is being stabilized

The ability to use if let expressions within match arm guards.

Example:

enum Command {
    Run(String),
    Stop,
    Pause,
}

fn process_command(cmd: Command, state: &mut String) {
    match cmd {
        Command::Run(name) if let Some(first_char) = name.chars().next() && first_char.is_ascii_alphabetic() => {
            // Both `name` and `first_char` are available here
            println!("Running command: {} (starts with '{}')", name, first_char);
            state.push_str(&format!("Running {}", name));
        }
        Command::Run(name) => {
            println!("Cannot run command '{}'. Invalid name.", name);
        }
        Command::Stop if state.contains("running") => {
            println!("Stopping current process.");
            state.clear();
        }
        _ => {
            println!("Unhandled command or state.");
        }
    }
}

Motivation

The primary motivation for if let guards is to reduce nesting and improve readability when conditional logic depends on pattern matching. Without this feature, such logic requires nested if let statements within match arms:

// Without if let guards
match value {
    Some(x) => {
        if let Ok(y) = compute(x) {
            // Both `x` and `y` are available here
            println!("{}, {}", x, y);
        }
    }
    _ => {}
}

// With if let guards
match value {
    Some(x) if let Ok(y) = compute(x) => {
        // Both `x` and `y` are available here
        println!("{}, {}", x, y);
    }
    _ => {}
}

Implementation and Testing

The feature has been implemented and tested comprehensively across different scenarios:

Core Functionality Tests

Scoping and variable binding:

  • scope.rs - Verifies that bindings created in if let guards are properly scoped and available in match arms
  • shadowing.rs - Tests that variable shadowing works correctly within guards
  • scoping-consistency.rs - Ensures temporaries in guards remain valid for the duration of their match arms

Type system integration:

  • type-inference.rs - Confirms type inference works correctly in if let guards
  • typeck.rs - Verifies type mismatches are caught appropriately

Pattern matching semantics:

Error Handling and Diagnostics

  • warns.rs - Tests warnings for irrefutable patterns and unreachable code in guards
  • parens.rs - Ensures parentheses around let expressions are properly rejected
  • macro-expanded.rs - Verifies macro expansions that produce invalid constructs are caught
  • guard-mutability-2.rs - Tests mutability and ownership violations in guards
  • ast-validate-guards.rs - Validates AST-level syntax restrictions

Drop Order and Temporaries

Key insight: Unlike let_chains in regular if expressions, if let guards do not have drop order inconsistencies because:

  1. Match guards are clearly scoped to their arms
  2. There is no "else block" equivalent that could cause temporal confusion

Edition Compatibility

This feature stabilizes on all editions, unlike let chains which was limited to edition 2024. This is safe because:

  1. if let guards don't suffer from the drop order issues that affected let chains in regular if expressions
  2. The scoping is unambiguous - guards are clearly tied to their match arms
  3. Extensive testing confirms identical behavior across all editions

Interactions with Future Features

The lang team has reviewed potential interactions with planned "guard patterns" and determined that stabilizing if let guards now does not create obstacles for future work. The scoping and evaluation semantics established here align with what guard patterns will need.

Unresolved Issues


Related:

@rustbot
Copy link
Collaborator

rustbot commented May 20, 2025

r? @SparrowLii

rustbot has assigned @SparrowLii.
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. labels May 20, 2025
@rustbot
Copy link
Collaborator

rustbot commented May 20, 2025

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

Some changes occurred in compiler/rustc_codegen_ssa

cc @WaffleLapkin

@Kivooeo Kivooeo force-pushed the if-let-guard-stable branch from 6fe74d9 to 5ee8970 Compare May 20, 2025 17:08
@rustbot
Copy link
Collaborator

rustbot commented May 20, 2025

rust-analyzer is developed in its own repository. If possible, consider making this change to rust-lang/rust-analyzer instead.

cc @rust-lang/rust-analyzer

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

@Kivooeo Kivooeo force-pushed the if-let-guard-stable branch from eb0e4b4 to 0358002 Compare May 20, 2025 17:13
@rust-log-analyzer

This comment has been minimized.

@Kivooeo Kivooeo force-pushed the if-let-guard-stable branch from 92a5204 to ab138ce Compare May 20, 2025 17:35
@rust-log-analyzer

This comment has been minimized.

@Kivooeo Kivooeo force-pushed the if-let-guard-stable branch from 5ceca48 to a20c4f6 Compare May 20, 2025 17:57
@rust-log-analyzer

This comment has been minimized.

@Kivooeo Kivooeo force-pushed the if-let-guard-stable branch 2 times, most recently from 1dd9974 to 5796073 Compare May 20, 2025 18:56
@traviscross traviscross added T-lang Relevant to the language team 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 May 20, 2025
@traviscross
Copy link
Contributor

cc @est31 @ehuss

@traviscross
Copy link
Contributor

cc @Nadrieril

@SparrowLii
Copy link
Member

SparrowLii commented May 21, 2025

This needs a fcp so I'd like to roll this to someone more familiar with this feature
r? compiler

@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label May 21, 2025
@rustbot rustbot assigned oli-obk and unassigned SparrowLii May 21, 2025
@traviscross traviscross added I-lang-nominated Nominated for discussion during a lang team meeting. P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang labels May 21, 2025
@oli-obk
Copy link
Contributor

oli-obk commented May 21, 2025

r? @est31

@theemathas
Copy link
Contributor

theemathas commented Aug 12, 2025

The pin!() macro (which internally uses super let) changes the order of drops. Is this a bug?

#![feature(if_let_guard)]

use std::pin::pin;

struct LoudDrop(&'static str);
impl Drop for LoudDrop {
    fn drop(&mut self) {
        println!("{}", self.0);
    }
}

fn main() {
    match () {
        _ if let x = LoudDrop("0")
            && let y = pin!(LoudDrop("1")) => {}
        _ => {}
    }
}

This outputs 0 then 1

My intuition is that, with regards to dropping, pin!(expr) should behave like &mut expr.

@Kivooeo
Copy link
Member Author

Kivooeo commented Aug 12, 2025

I tested this with regular let chains (outside of if-let guards) and observed the same behavior - pin!() changes the drop order

if let x = LoudDrop("0")
    && let y = pin!(LoudDrop("1"))
{}

This suggests the issue might be related to let chains in general, or specifically to the interaction between let chains and "super let". If I recall correctly, super let was implemented after let chains were stabilized, which could explain this interaction

@Kivooeo
Copy link
Member Author

Kivooeo commented Aug 12, 2025

I did some additional testing and found that the drop order follows a predictable pattern (though this doesn't change the fact that pin!() changes the expected order). Should we consider documenting this behavior?

    if let x = LoudDrop("0")
        && let y = pin!(LoudDrop("1"))
        && let z = pin!(LoudDrop("2"))
        && let w = LoudDrop("3")
    {}
    // 3
    // 0
    // 2
    // 1

The pattern appears to be: drops occur from bottom to top, but any pinned values are moved to the end of the drop queue

@theemathas
Copy link
Contributor

I've filed this as #145328

github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Aug 13, 2025
add a scope for `if let` guard temporaries and bindings

This fixes my concern with `if let` guard drop order, namely that the guard's bindings and temporaries were being dropped after their arm's pattern's bindings, instead of before (rust-lang/rust#141295 (comment)). The guard's bindings and temporaries now live in a new scope, which extends until (but not past) the end of the arm, guaranteeing they're dropped before the arm's pattern's bindings.

This only introduces a new scope for match arms with guards. Perf results (rust-lang/rust#143376 (comment)) seemed to indicate there wasn't a significant hit to introduce a new scope on all match arms, but guard patterns (rust-lang/rust#129967) will likely benefit from only adding new scopes when necessary (with some patterns requiring multiple nested scopes).

Tracking issue for `if_let_guard`: rust-lang/rust#51114

Tests are adapted from examples by `@traviscross,` `@est31,` and myself on rust-lang/rust#141295.
rust-cloud-vms bot pushed a commit to makai410/rustc_public that referenced this pull request Aug 16, 2025
add a scope for `if let` guard temporaries and bindings

This fixes my concern with `if let` guard drop order, namely that the guard's bindings and temporaries were being dropped after their arm's pattern's bindings, instead of before (rust-lang/rust#141295 (comment)). The guard's bindings and temporaries now live in a new scope, which extends until (but not past) the end of the arm, guaranteeing they're dropped before the arm's pattern's bindings.

This only introduces a new scope for match arms with guards. Perf results (rust-lang/rust#143376 (comment)) seemed to indicate there wasn't a significant hit to introduce a new scope on all match arms, but guard patterns (rust-lang/rust#129967) will likely benefit from only adding new scopes when necessary (with some patterns requiring multiple nested scopes).

Tracking issue for `if_let_guard`: rust-lang/rust#51114

Tests are adapted from examples by `@traviscross,` `@est31,` and myself on rust-lang/rust#141295.
github-actions bot pushed a commit to rust-lang/stdarch that referenced this pull request Aug 18, 2025
add a scope for `if let` guard temporaries and bindings

This fixes my concern with `if let` guard drop order, namely that the guard's bindings and temporaries were being dropped after their arm's pattern's bindings, instead of before (rust-lang/rust#141295 (comment)). The guard's bindings and temporaries now live in a new scope, which extends until (but not past) the end of the arm, guaranteeing they're dropped before the arm's pattern's bindings.

This only introduces a new scope for match arms with guards. Perf results (rust-lang/rust#143376 (comment)) seemed to indicate there wasn't a significant hit to introduce a new scope on all match arms, but guard patterns (rust-lang/rust#129967) will likely benefit from only adding new scopes when necessary (with some patterns requiring multiple nested scopes).

Tracking issue for `if_let_guard`: rust-lang/rust#51114

Tests are adapted from examples by `@traviscross,` `@est31,` and myself on rust-lang/rust#141295.
rust-cloud-vms bot pushed a commit to makai410/rustc_public that referenced this pull request Aug 20, 2025
add a scope for `if let` guard temporaries and bindings

This fixes my concern with `if let` guard drop order, namely that the guard's bindings and temporaries were being dropped after their arm's pattern's bindings, instead of before (rust-lang/rust#141295 (comment)). The guard's bindings and temporaries now live in a new scope, which extends until (but not past) the end of the arm, guaranteeing they're dropped before the arm's pattern's bindings.

This only introduces a new scope for match arms with guards. Perf results (rust-lang/rust#143376 (comment)) seemed to indicate there wasn't a significant hit to introduce a new scope on all match arms, but guard patterns (rust-lang/rust#129967) will likely benefit from only adding new scopes when necessary (with some patterns requiring multiple nested scopes).

Tracking issue for `if_let_guard`: rust-lang/rust#51114

Tests are adapted from examples by `@traviscross,` `@est31,` and myself on rust-lang/rust#141295.
github-actions bot pushed a commit to rust-lang/compiler-builtins that referenced this pull request Aug 28, 2025
add a scope for `if let` guard temporaries and bindings

This fixes my concern with `if let` guard drop order, namely that the guard's bindings and temporaries were being dropped after their arm's pattern's bindings, instead of before (rust-lang/rust#141295 (comment)). The guard's bindings and temporaries now live in a new scope, which extends until (but not past) the end of the arm, guaranteeing they're dropped before the arm's pattern's bindings.

This only introduces a new scope for match arms with guards. Perf results (rust-lang/rust#143376 (comment)) seemed to indicate there wasn't a significant hit to introduce a new scope on all match arms, but guard patterns (rust-lang/rust#129967) will likely benefit from only adding new scopes when necessary (with some patterns requiring multiple nested scopes).

Tracking issue for `if_let_guard`: rust-lang/rust#51114

Tests are adapted from examples by `@traviscross,` `@est31,` and myself on rust-lang/rust#141295.
@Urgau Urgau added S-waiting-on-t-lang Status: Awaiting decision from T-lang and removed S-waiting-on-team DEPRECATED: Use the team-based variants `S-waiting-on-t-lang`, `S-waiting-on-t-compiler`, ... labels Oct 6, 2025
@Kivooeo
Copy link
Member Author

Kivooeo commented Oct 12, 2025

Hi everyone,

It’s been a while since my last update - quite a lot has happened in the meantime, but the main thing is I'm still here

I’ve noticed that many fixes, including the main concern, have already been merged - big thanks to @dianne for the help, and to @theemathas for spotting some interesting edge cases

Also, the documentation (thanks again to dianne) has been created and marked as waiting-on-stabilization. Am I right in assuming that means it’s in good shape?

So from what I see, the major concerns like the drop order bug and the docs are resolved

I just wanted to check with the team: are there any other blockers before I start extending the test suite and working through the conflicts?

@traviscross
Copy link
Contributor

traviscross commented Oct 12, 2025

Thanks for checking in and following up on this. Looking back over the issue, I agree that the primary blockers here have been resolved. I would indeed suggest extending the tests and rebasing the branch.

We haven't yet approved the Reference PR (rust-lang/reference#1957), and that will need to happen before we merge this PR, but on a skim, it looks to be in good shape; this won't hold things up unduly. We'll probably want to see the extended tests before approving this anyway, so as to compare the behavior with what we're documenting.

If you could leave a note here when the tests are extended and the branch is rebased, I'll plan to have a look, resolve the concern I had filed, restart the proposed FCP, and finish reviewing the Reference PR.

@rust-cloud-vms rust-cloud-vms bot force-pushed the if-let-guard-stable branch from bde3195 to 902b4d2 Compare October 13, 2025 06:16
@rustbot
Copy link
Collaborator

rustbot commented Oct 13, 2025

This PR modifies tests/ui/issues/. If this PR is adding new tests to tests/ui/issues/,
please refrain from doing so, and instead add it to more descriptive subdirectories.

@rustbot rustbot added the T-clippy Relevant to the Clippy team. label Oct 13, 2025
@rustbot
Copy link
Collaborator

rustbot commented Oct 13, 2025

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@rust-log-analyzer

This comment has been minimized.

@rust-cloud-vms rust-cloud-vms bot force-pushed the if-let-guard-stable branch from 902b4d2 to 54371e6 Compare October 13, 2025 06:52
@rust-log-analyzer

This comment has been minimized.

@rust-cloud-vms rust-cloud-vms bot force-pushed the if-let-guard-stable branch from 54371e6 to 725c559 Compare October 13, 2025 07:26
@rustbot
Copy link
Collaborator

rustbot commented Oct 13, 2025

The Miri subtree was changed

cc @rust-lang/miri

@rust-log-analyzer

This comment has been minimized.

@rust-cloud-vms rust-cloud-vms bot force-pushed the if-let-guard-stable branch from 725c559 to 5650d71 Compare October 13, 2025 07:47
@rust-log-analyzer

This comment has been minimized.

@rust-cloud-vms rust-cloud-vms bot force-pushed the if-let-guard-stable branch from 5650d71 to 3a6c8c8 Compare October 13, 2025 12:49
@Kivooeo
Copy link
Member Author

Kivooeo commented Oct 13, 2025

Conflicts are resolved

For test part from what I see is dianne added a test for drop order which is covering the drop order fix, I updated description and specify 3 new tests in there (one of them I adopted from here #141295 (comment))

And as for tests for this and this I will add them later on if noone else will do this before, and point them in unresolved issues as non blocking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. A-meta Area: Issues & PRs about the rust-lang/rust repository itself A-rustc-dev-guide Area: rustc-dev-guide A-testsuite Area: The testsuite used to check the correctness of rustc A-tidy Area: The tidy tool disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. 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. proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. S-blocked Status: Blocked on something else such as an RFC or other implementation work. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. S-waiting-on-documentation Status: Waiting on approved PRs to documentation before merging S-waiting-on-t-lang Status: Awaiting decision from T-lang T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) T-clippy Relevant to the Clippy team. T-infra Relevant to the infrastructure team, which will review and decide on the PR/issue. T-lang Relevant to the language team WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)

Projects

None yet

Development

Successfully merging this pull request may close these issues.