Skip to content

if let guards documentation #1823

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 1 commit into
base: master
Choose a base branch
from
Open

Conversation

Kivooeo
Copy link

@Kivooeo Kivooeo commented May 10, 2025

It’s been a while since the original PR for if let guards was opened, so I decided to take the initiative and push it a bit closer to stabilization

This PR adds some documentation — I did my best to keep it consistent with the existing docs on if guards
There might still be some rough edges or small inaccuracies, so feel free to point them out

It’s also my first time contributing to the docs, so any feedback is appreciated!

Tracking issue rust-lang/rust#51114
Stabilization: rust-lang/rust#141295

rendered

@rustbot rustbot added the S-waiting-on-review Status: The marked PR is awaiting review from a maintainer label May 10, 2025
@Kivooeo Kivooeo force-pushed the master branch 6 times, most recently from 65337a7 to e704b43 Compare May 10, 2025 19:44
Comment on lines +159 to +173
```rust,ignore
match expression {
pattern if let subpattern = guard_expr => arm_body,
...
}
```
Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to make a working example?

Copy link
Author

Choose a reason for hiding this comment

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

Im not really sure, because it's not stable feature and contains behind #![feature...] gate that's aren't allowed in documentation and CI will fail with this error

Running target/debug/style-check ../src
error in ../src/expressions/match-expr.md: #![feature] attributes are not allowed

* The `if let` guard may refer to variables bound by the outer match pattern.
* New variables bound inside the `if let` guard (e.g., `y` in the example above) are available within the body of the match arm where the guard evaluates to `true`, but are not accessible in other arms or outside the match expression.

```rust,ignore
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
```rust,ignore
```rust

2. Guard evaluation happens after that, and:
* It runs using a shared borrow of the scrutinee
* You cannot move from the scrutinee inside the guard.
* New bindings created inside the guard (e.g., via `if let Some(y) = expr`) are local to the guard and do not persist into the match arm body.
Copy link
Member

Choose a reason for hiding this comment

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

What does that mean?

Copy link
Author

@Kivooeo Kivooeo May 16, 2025

Choose a reason for hiding this comment

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

I've updated it and it now should make more sense

```
> [!NOTE]
> Unlike regular if guards, `if let` guards execute only once per match arm, even if the pattern uses the `|` operator to match multiple patterns. This avoids repeated evaluation and potential side effects.
> ```rust,ignore
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
> ```rust,ignore
> ```rust

@Kivooeo Kivooeo force-pushed the master branch 4 times, most recently from d925c41 to 303dab2 Compare May 16, 2025 15:55
@Kivooeo
Copy link
Author

Kivooeo commented May 16, 2025

@WaffleLapkin not sure if you got notifications when i update something, but i update almost all your reviews expect ones that asks to make code runnable, because im not sure if this is possible, we can remove ignore after it will stabilize, is that fine?

@ehuss ehuss added the S-waiting-on-stabilization Waiting for a stabilization PR to be merged in the main Rust repository label Jun 3, 2025
workingjubilee added a commit to workingjubilee/rustc that referenced this pull request Jun 7, 2025
…31,fee1-dead

Stabilize `if let` guards (`feature(if_let_guard)`)

## Summary

This proposes the stabilization of `if let` guards (tracking issue: rust-lang#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:

```rust
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:

```rust
// 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`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/scope.rs) - Verifies that bindings created in `if let` guards are properly scoped and available in match arms
- [`shadowing.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/shadowing.rs) - Tests that variable shadowing works correctly within guards
- [`scoping-consistency.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/scoping-consistency.rs) - Ensures temporaries in guards remain valid for the duration of their match arms

**Type system integration:**
- [`type-inference.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/type-inference.rs) - Confirms type inference works correctly in `if let` guards
- [`typeck.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/typeck.rs) - Verifies type mismatches are caught appropriately

**Pattern matching semantics:**
- [`exhaustive.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/exhaustive.rs) - Validates that `if let` guards are correctly handled in exhaustiveness analysis
- [`move-guard-if-let.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let.rs) and [`move-guard-if-let-chain.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/move-guard-if-let-chain.rs) - Test that conditional moves in guards are tracked correctly by the borrow checker

### Error Handling and Diagnostics

- [`warns.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/warns.rs) - Tests warnings for irrefutable patterns and unreachable code in guards
- [`parens.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/parens.rs) - Ensures parentheses around `let` expressions are properly rejected
- [`macro-expanded.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/macro-expanded.rs) - Verifies macro expansions that produce invalid constructs are caught
- [`guard-mutability-2.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/guard-mutability-2.rs) - Tests mutability and ownership violations in guards
- [`ast-validate-guards.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2497-if-let-chains/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

- [`drop-order.rs`](https://github.com/rust-lang/rust/blob/5796073c134eaac30475f9a19462c4e716c9119c/tests/ui/rfcs/rfc-2294-if-let-guard/drop-order.rs) - Tests that temporaries in guards are dropped at the correct time
- [`compare-drop-order.rs`](https://github.com/rust-lang/rust/blob/aef3f5fdf052fbbc16e174aef5da6d50832ca316/tests/ui/rfcs/rfc-2294-if-let-guard/compare-drop-order.rs) - Compares drop order between `if let` guards and nested `if let` in match arms, confirming they behave identically across all editions
- rust-lang#140981 - A complicated drop order test involved `let chain` was made by `@est31`

## 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

All blocking issues have been resolved:
- [x] - rust-lang#140981
- [x] - added tests description by `@jieyouxu` request
- [x] - Concers from `@scottmcm` about stabilizing this across all editions
- [x] - check if drop order in all edition when using `let chains` inside `if let` guard is the same
- [x] - interactions with guard patters

---

**Related:**
- Tracking Issue: rust-lang#51114
- RFC: rust-lang/rfcs#2294
- Documentation PR: rust-lang/reference#1823
@workingjubilee
Copy link
Member

@rustbot label -S-waiting-on-stabilization

@rustbot rustbot removed the S-waiting-on-stabilization Waiting for a stabilization PR to be merged in the main Rust repository label Jun 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: The marked PR is awaiting review from a maintainer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants