Skip to content

Commit e518d9d

Browse files
authored
Merge pull request #2294 from strake/if-let-guard
propose if let guard
2 parents 5d2beda + ebc031e commit e518d9d

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed

text/2294-if-let-guard.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
- Feature Name: `if_let_guard`
2+
- Start Date: 2018-01-15
3+
- RFC PR: [rust-lang/rfcs#2294](https://github.com/rust-lang/rfcs/pull/2294)
4+
- Rust Issue: [rust-lang/rust#51114](https://github.com/rust-lang/rust/issues/51114)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Allow `if let` guards in `match` expressions.
10+
11+
# Motivation
12+
[motivation]: #motivation
13+
14+
This feature would greatly simplify some logic where we must match a pattern iff some value computed from the `match`-bound values has a certain form, where said value may be costly or impossible (due to affine semantics) to recompute in the match arm.
15+
16+
For further motivation, see the example in the guide-level explanation. Absent this feature, we might rather write the following:
17+
```rust
18+
match ui.wait_event() {
19+
KeyPress(mod_, key, datum) =>
20+
if let Some(action) = intercept(mod_, key) { act(action, datum) }
21+
else { accept!(KeyPress(mod_, key, datum)) /* can't re-use event verbatim if `datum` is non-`Copy` */ }
22+
ev => accept!(ev),
23+
}
24+
```
25+
26+
`accept` may in general be lengthy and inconvenient to move into another function, for example if it refers to many locals.
27+
28+
Here is an (incomplete) example taken from a real codebase, to respond to ANSI CSI escape sequences:
29+
30+
```rust
31+
#[inline]
32+
fn csi_dispatch(&mut self, parms: &[i64], ims: &[u8], ignore: bool, x: char) {
33+
match x {
34+
'C' => if let &[n] = parms { self.screen.move_x( n as _) }
35+
else { log_debug!("Unknown CSI sequence: {:?}, {:?}, {:?}, {:?}",
36+
parms, ims, ignore, x) },
37+
'D' => if let &[n] = parms { self.screen.move_x(-n as _) }
38+
else { log_debug!("Unknown CSI sequence: {:?}, {:?}, {:?}, {:?}",
39+
parms, ims, ignore, x) },
40+
'J' => self.screen.erase(match parms {
41+
&[] |
42+
&[0] => Erasure::ScreenFromCursor,
43+
&[1] => Erasure::ScreenToCursor,
44+
&[2] => Erasure::Screen,
45+
_ => { log_debug!("Unknown CSI sequence: {:?}, {:?}, {:?}, {:?}",
46+
parms, ims, ignore, x); return },
47+
}, false),
48+
'K' => self.screen.erase(match parms {
49+
&[] |
50+
&[0] => Erasure::LineFromCursor,
51+
&[1] => Erasure::LineToCursor,
52+
&[2] => Erasure::Line,
53+
_ => { log_debug!("Unknown CSI sequence: {:?}, {:?}, {:?}, {:?}",
54+
parms, ims, ignore, x); return },
55+
}, false),
56+
'm' => match parms {
57+
&[] |
58+
&[0] => *self.screen.def_attr_mut() = Attr { fg_code: 0, fg_rgb: [0xFF; 3],
59+
bg_code: 0, bg_rgb: [0x00; 3],
60+
flags: AttrFlags::empty() },
61+
&[n] => if let (3, Some(rgb)) = (n / 10, color_for_code(n % 10, 0xFF)) {
62+
self.screen.def_attr_mut().fg_rgb = rgb;
63+
} else {
64+
log_debug!("Unknown CSI sequence: {:?}, {:?}, {:?}, {:?}",
65+
parms, ims, ignore, x);
66+
},
67+
_ => log_debug!("Unknown CSI sequence: {:?}, {:?}, {:?}, {:?}",
68+
parms, ims, ignore, x),
69+
},
70+
_ => log_debug!("Unknown CSI sequence: {:?}, {:?}, {:?}, {:?}",
71+
parms, ims, ignore, x),
72+
}
73+
}
74+
```
75+
76+
These examples are both clearer with `if let` guards as follows. Particularly in the latter example, in the author's opinion, the control flow is easier to follow.
77+
78+
# Guide-level explanation
79+
[guide-level-explanation]: #guide-level-explanation
80+
81+
*(Adapted from Rust book)*
82+
83+
A *match guard* is an `if let` condition specified after the pattern in a `match` arm that also must match if the pattern matches in order for that arm to be chosen. Match guards are useful for expressing more complex ideas than a pattern alone allows.
84+
85+
The condition can use variables created in the pattern, and the match arm can use any variables bound in the `if let` pattern (as well as any bound in the `match` pattern, unless the `if let` expression moves out of them).
86+
87+
Let us consider an example which accepts a user-interface event (e.g. key press, pointer motion) and follows 1 of 2 paths: either we intercept it and take some action or deal with it normally (whatever that might mean here):
88+
```rust
89+
match ui.wait_event() {
90+
KeyPress(mod_, key, datum) if let Some(action) = intercept(mod_, key) => act(action, datum),
91+
ev => accept!(ev),
92+
}
93+
```
94+
95+
Here is another example, to respond to ANSI CSI escape sequences:
96+
97+
```rust
98+
#[inline]
99+
fn csi_dispatch(&mut self, parms: &[i64], ims: &[u8], ignore: bool, x: char) {
100+
match x {
101+
'C' if let &[n] = parms => self.screen.move_x( n as _),
102+
'D' if let &[n] = parms => self.screen.move_x(-n as _),
103+
_ if let Some(e) = erasure(x, parms) => self.screen.erase(e, false),
104+
'm' => match parms {
105+
&[] |
106+
&[0] => *self.screen.def_attr_mut() = Attr { fg_code: 0, fg_rgb: [0xFF; 3],
107+
bg_code: 0, bg_rgb: [0x00; 3],
108+
flags: AttrFlags::empty() },
109+
&[n] if let (3, Some(rgb)) = (n / 10, color_for_code(n % 10, 0xFF)) =>
110+
self.screen.def_attr_mut().fg_rgb = rgb,
111+
_ => log_debug!("Unknown CSI sequence: {:?}, {:?}, {:?}, {:?}",
112+
parms, ims, ignore, x),
113+
},
114+
_ => log_debug!("Unknown CSI sequence: {:?}, {:?}, {:?}, {:?}",
115+
parms, ims, ignore, x),
116+
}
117+
}
118+
119+
#[inline]
120+
fn erasure(x: char, parms: &[i64]) -> Option<Erasure> {
121+
match x {
122+
'J' => match parms {
123+
&[] |
124+
&[0] => Some(Erasure::ScreenFromCursor),
125+
&[1] => Some(Erasure::ScreenToCursor),
126+
&[2] => Some(Erasure::Screen),
127+
_ => None,
128+
},
129+
'K' => match parms {
130+
&[] |
131+
&[0] => Some(Erasure::LineFromCursor),
132+
&[1] => Some(Erasure::LineToCursor),
133+
&[2] => Some(Erasure::Line),
134+
_ => None,
135+
},
136+
_ => None,
137+
}
138+
}
139+
```
140+
141+
142+
# Reference-level explanation
143+
[reference-level-explanation]: #reference-level-explanation
144+
145+
This proposal would introduce syntax for a match arm: `pat if let guard_pat = guard_expr => body_expr` with semantics so the arm is chosen iff the argument of `match` matches `pat` and `guard_expr` matches `guard_pat`. The variables of `pat` are bound in `guard_expr`, and the variables of `pat` and `guard_pat` are bound in `body_expr`. The syntax is otherwise the same as for `if` guards. (Indeed, `if` guards become effectively syntactic sugar for `if let` guards.)
146+
147+
An arm may not have both an `if` and an `if let` guard.
148+
149+
# Drawbacks
150+
[drawbacks]: #drawbacks
151+
152+
* It further complicates the grammar.
153+
* It is ultimately syntactic sugar, but the transformation to present Rust is potentially non-obvious.
154+
155+
# Rationale and alternatives
156+
[alternatives]: #alternatives
157+
158+
* The chief alternatives are to rewrite the guard as an `if` guard and a bind in the match arm, or in some cases into the argument of `match`; or to write the `if let` in the match arm and copy the rest of the `match` into the `else` branch — what can be done with this syntax can already be done in Rust (to the author's knowledge); this proposal is purely ergonomic, but in the author's opinion, the ergonomic win is significant.
159+
* The proposed syntax feels natural by analogy to the `if` guard syntax we already have, as between `if` and `if let` expressions. No alternative syntaxes were considered.
160+
161+
# Unresolved questions
162+
[unresolved]: #unresolved-questions
163+
164+
Questions in scope of this proposal: none yet known
165+
166+
Questions out of scope:
167+
168+
* Should we allow multiple guards? This proposal allows only a single `if let` guard. One can combine `if` guards with `&&`[an RFC](https://github.com/rust-lang/rfcs/issues/929) to allow `&&` in `if let` already is, so we may want to follow that in future for `if let` guards also.
169+
* What happens if `guard_expr` moves out of `pat` but fails to match? This is already a question for `if` guards and (to the author's knowledge) not formally specified anywhere — this proposal (implicitly) copies that behavior.

0 commit comments

Comments
 (0)