Skip to content

Commit a1cf397

Browse files
committed
added if let guards documentation
1 parent db04a2a commit a1cf397

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

src/expressions/match-expr.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ MatchArms ->
1818
MatchArm -> OuterAttribute* Pattern MatchArmGuard?
1919
2020
MatchArmGuard -> `if` Expression
21+
| if Expression
22+
| if let Pattern = Expression
2123
```
2224
<!-- TODO: The exception above isn't accurate, see https://github.com/rust-lang/reference/issues/569 -->
2325

@@ -150,6 +152,105 @@ This allows shared borrows to be used inside guards without moving out of the sc
150152
r[expr.match.guard.no-mutation]
151153
Moreover, by holding a shared reference while evaluating the guard, mutation inside guards is also prevented.
152154
155+
r[expr.match.if.let.guard]
156+
## If Let Guards
157+
Match arms can include `if let` guards to allow conditional pattern matching within the guard clause. This feature is currently unstable and requires the `#![feature(if_let_guard)]` attribute. It is tracked in issue [#51114](https://github.com/rust-lang/rust/issues/51114).
158+
159+
r[expr.match.if.let.guard.syntax]
160+
```rust
161+
match expression {
162+
pattern if let subpattern = guard_expr => arm_body,
163+
...
164+
}
165+
```
166+
Here, `guard_expr` is evaluated and matched against `subpattern`. If the match succeeds, the guard evaluates to `true` and the arm is selected. Otherwise, pattern matching continues to the next arm.
167+
168+
r[expr.match.if.let.guard.behavior]
169+
When the pattern matches successfully, the `if let` expression in the guard is evaluated:
170+
* If the inner pattern (`subpattern`) matches the result of `guard_expr`, the guard evaluates to `true`.
171+
* Otherwise, the next arm is tested.
172+
173+
```rust
174+
#![feature(if_let_guard)]
175+
let value = Some(10);
176+
177+
let msg = match value {
178+
Some(x) if let Some(y) = Some(x - 1) => format!("Matched inner value: {}", y),
179+
_ => "No match".to_string(),
180+
};
181+
182+
```
183+
184+
r[expr.match.if.let.guard.scope]
185+
* The `if let` guard may refer to variables bound by the outer match pattern.
186+
* 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.
187+
188+
```rust
189+
#![feature(if_let_guard)]
190+
191+
let opt = Some(42);
192+
193+
match opt {
194+
Some(x) if let Some(y) = Some(x + 1) => {
195+
// Both `x` and `y` are available in this arm,
196+
// since the pattern matched and the guard evaluated to true.
197+
println!("x = {}, y = {}", x, y);
198+
}
199+
_ => {
200+
// `y` is not available here — it was only bound inside the guard above.
201+
// Uncommenting the line below will cause a compile-time error:
202+
// println!("{}", y); // error: cannot find value `y` in this scope
203+
}
204+
}
205+
206+
// Outside the match expression, neither `x` nor `y` are in scope.
207+
```
208+
209+
* The outer pattern variables (`x`) follow the same borrowing behavior as in standard match guards (see below).
210+
211+
r[expr.match.if.let.guard.borrowing]
212+
Before a guard (including an `if let` guard) is evaluated:
213+
1. Pattern bindings are performed first
214+
Variables from the outer match pattern (e.g., `x` in `Some(x)`) are bound and initialized. These bindings may involve moving, copying, or borrowing values from the scrutinee.
215+
```rust
216+
match Some(String::from("hello")) {
217+
Some(s) if /* guard */ => { /* s is moved here */ }
218+
_ => {}
219+
}
220+
```
221+
2. Guard evaluation happens after that, and:
222+
* It runs using a shared borrow of the scrutinee
223+
* You cannot move from the scrutinee inside the guard.
224+
* 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.
225+
```rust
226+
#![feature(if_let_guard)]
227+
228+
let val = Some(vec![1, 2, 3]);
229+
230+
let result = match val {
231+
Some(v) if let Some(_) = take(v) => "ok", // ERROR: cannot move out of `v`
232+
_ => "nope",
233+
};
234+
235+
```
236+
In the above example, `v` is already bound in the outer pattern, and the guard attempts to move itthis is not allowed. You can fix it by cloning or borrowing:
237+
```rust
238+
Some(v) if let Some(_) = take(v.clone()) => "ok",
239+
```
240+
> [!NOTE]
241+
> 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.
242+
> ```rust
243+
> #![feature(if_let_guard)]
244+
> use std::cell::Cell;
245+
>
246+
> let i: Cell<i32> = Cell::new(0);
247+
> match 1 {
248+
> 1 | _ if let Some(_) = { i.set(i.get() + 1); Some(1) } => {}
249+
> _ => {}
250+
> }
251+
> assert_eq!(i.get(), 1); // Guard not executed twice
252+
> ```
253+
153254
r[expr.match.attributes]
154255
## Attributes on match arms
155256

0 commit comments

Comments
 (0)