Skip to content

Commit 0f5f792

Browse files
committed
added documentation for if let
1 parent cb3e610 commit 0f5f792

File tree

1 file changed

+98
-3
lines changed

1 file changed

+98
-3
lines changed

src/expressions/match-expr.md

+98-3
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@ MatchExpression ->
99
MatchArms?
1010
`}`
1111
12-
Scrutinee -> Expression _except [StructExpression]_
12+
Scrutinee ->
13+
Expression _except_ [StructExpression]
1314
1415
MatchArms ->
1516
( MatchArm `=>` ( ExpressionWithoutBlock `,` | ExpressionWithBlock `,`? ) )*
1617
MatchArm `=>` Expression `,`?
1718
18-
MatchArm -> OuterAttribute* Pattern MatchArmGuard?
19+
MatchArm ->
20+
OuterAttribute* Pattern MatchArmGuard?
1921
20-
MatchArmGuard -> `if` Expression
22+
MatchArmGuard ->
23+
`if` Expression
24+
| `if` Expression `&&` LetChain
2125
```
2226
<!-- TODO: The exception above isn't accurate, see https://github.com/rust-lang/reference/issues/569 -->
2327

@@ -150,6 +154,97 @@ This allows shared borrows to be used inside guards without moving out of the sc
150154
r[expr.match.guard.no-mutation]
151155
Moreover, by holding a shared reference while evaluating the guard, mutation inside guards is also prevented.
152156
157+
r[expr.match.if.let.guard]
158+
## If Let Guards
159+
Match arms can include `if let` guards to allow conditional pattern matching within the guard clause.
160+
161+
r[expr.match.if.let.guard.syntax]
162+
```rust,ignore
163+
match expression {
164+
pattern if let subpattern = guard_expr => arm_body,
165+
...
166+
}
167+
```
168+
Here, `guard_expr` is evaluated and matched against `subpattern`. If the `if let` expression in the guard matches successfully and the arm’s body is executed. Otherwise, pattern matching continues to the next arm.
169+
170+
r[expr.match.if.let.guard.behavior]
171+
When the pattern matches successfully, the `if let` expression in the guard is evaluated:
172+
* The guard proceeds if the inner pattern (`subpattern`) matches the result of `guard_expr`.
173+
* Otherwise, the next arm is tested.
174+
175+
```rust,ignore
176+
let value = Some(10);
177+
178+
let msg = match value {
179+
Some(x) if let Some(y) = Some(x - 1) => format!("Matched inner value: {}", y),
180+
_ => "No match".to_string(),
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,ignore
189+
let opt = Some(42);
190+
191+
match opt {
192+
Some(x) if let Some(y) = Some(x + 1) => {
193+
// Both `x` and `y` are available in this arm,
194+
// since the pattern matched and the guard evaluated to true.
195+
println!("x = {}, y = {}", x, y);
196+
}
197+
_ => {
198+
// `y` is not available here --- it was only bound inside the guard above.
199+
// Uncommenting the line below will cause a compile-time error:
200+
// println!("{}", y); // error: cannot find value `y` in this scope
201+
}
202+
}
203+
204+
// Outside the match expression, neither `x` nor `y` are in scope.
205+
```
206+
207+
* The outer pattern variables (`x`) follow the same borrowing behavior as in standard match guards (see below).
208+
209+
r[expr.match.if.let.guard.borrowing]
210+
Before a guard (including an `if let` guard) is evaluated:
211+
1. Pattern bindings are performed first
212+
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.
213+
```rust,ignore
214+
match Some(String::from("hello")) {
215+
Some(s) if /* guard */ => { /* s is moved here */ }
216+
_ => {}
217+
}
218+
```
219+
2. Guard evaluation happens after that, and:
220+
* It runs using a shared borrow of the scrutinee
221+
* You cannot move from the scrutinee inside the guard.
222+
* 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.
223+
```rust,ignore
224+
let val = Some(vec![1, 2, 3]);
225+
226+
let result = match val {
227+
Some(v) if let Some(_) = take(v) => "ok", // ERROR: cannot move out of `v`
228+
_ => "nope",
229+
};
230+
```
231+
In the above example, `v` is already bound in the outer pattern, and the guard attempts to move it --- this is not allowed. You can fix it by cloning or borrowing:
232+
```rust,ignore
233+
Some(v) if let Some(_) = take(v.clone()) => "ok",
234+
```
235+
> [!NOTE]
236+
> Multiple matches using the `|` operator can cause the pattern guard and the side effects it has to execute multiple times. For example:
237+
> ```rust,ignore
238+
> use std::cell::Cell;
239+
>
240+
> let i: Cell<i32> = Cell::new(0);
241+
> match 1 {
242+
> 1 | _ if let Some(_) = { i.set(i.get() + 1); Some(1) } => {}
243+
> _ => {}
244+
> }
245+
> assert_eq!(i.get(), 2); // Guard is executed twice
246+
> ```
247+
153248
r[expr.match.attributes]
154249
## Attributes on match arms
155250

0 commit comments

Comments
 (0)