Skip to content

Commit 2431a9a

Browse files
authored
Merge pull request #2175 from Centril/rfc/if-while-or-patterns
RFC: or-patterns in if / while let expressions
2 parents 6f82eb1 + ea0b302 commit 2431a9a

File tree

1 file changed

+383
-0
lines changed

1 file changed

+383
-0
lines changed

text/2175-if-while-or-patterns.md

Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
- Feature Name: if_while_or_patterns
2+
- Start Date: 2017-10-16
3+
- RFC PR: https://github.com/rust-lang/rfcs/pull/2175
4+
- Rust Issue: https://github.com/rust-lang/rust/issues/48215
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
[`if let`]: https://github.com/rust-lang/rfcs/pull/160
10+
[`while let`]: https://github.com/rust-lang/rfcs/pull/214
11+
12+
Enables "or" patterns for [`if let`] and [`while let`] expressions as well as
13+
`let` statements. In other words, examples like the following are now possible:
14+
15+
```rust
16+
enum E<T> {
17+
A(T), B(T), C, D, E, F
18+
}
19+
20+
// Assume the enum E and the following for the remainder of the RFC:
21+
use E::*;
22+
23+
let x = A(1);
24+
let r = if let C | D = x { 1 } else { 2 };
25+
26+
while let A(x) | B(x) = source() {
27+
react_to(x);
28+
}
29+
30+
enum ParameterKind<T, L = T> { Ty(T), Lifetime(L), }
31+
32+
// Only possible when `L = T` such that `kind : ParameterKind<T, T>`.
33+
let Ty(x) | Lifetime(x) = kind;
34+
```
35+
36+
# Motivation
37+
[motivation]: #motivation
38+
39+
While nothing in this RFC is currently impossible in Rust, the changes the RFC
40+
proposes improves the ergonomics of control flow when dealing with `enum`s
41+
(sum types) with three or more variants where the program should react in one
42+
way to a group of variants, and another way to another group of variants.
43+
Examples of when such sum types occur are protocols, when dealing with
44+
languages (ASTs), and non-trivial iterators.
45+
46+
The following snippet (written with this RFC):
47+
48+
```rust
49+
if let A(x) | B(x) = expr {
50+
do_stuff_with(x);
51+
}
52+
```
53+
54+
must be written as:
55+
56+
```rust
57+
if let A(x) = expr {
58+
do_stuff_with(x);
59+
} else if let B(x) = expr {
60+
do_stuff_with(x);
61+
}
62+
```
63+
64+
or, using `match`:
65+
66+
```rust
67+
match expr {
68+
A(x) | B(x) => do_stuff_with(x),
69+
_ => {},
70+
}
71+
```
72+
73+
[`std::iter`]: https://doc.rust-lang.org/nightly/src/core/iter/mod.rs.html#691
74+
75+
This way of using `match` is seen multiple times in [`std::iter`] when dealing
76+
with the `Chain` iterator adapter. An example of this is:
77+
78+
```rust
79+
fn fold<Acc, F>(self, init: Acc, mut f: F) -> Acc
80+
where F: FnMut(Acc, Self::Item) -> Acc,
81+
{
82+
let mut accum = init;
83+
match self.state {
84+
ChainState::Both | ChainState::Front => {
85+
accum = self.a.fold(accum, &mut f);
86+
}
87+
_ => { }
88+
}
89+
match self.state {
90+
ChainState::Both | ChainState::Back => {
91+
accum = self.b.fold(accum, &mut f);
92+
}
93+
_ => { }
94+
}
95+
accum
96+
}
97+
```
98+
99+
which could have been written as:
100+
101+
```rust
102+
fn fold<Acc, F>(self, init: Acc, mut f: F) -> Acc
103+
where F: FnMut(Acc, Self::Item) -> Acc,
104+
{
105+
use ChainState::*;
106+
let mut accum = init;
107+
if let Both | Front = self.state { accum = self.a.fold(accum, &mut f); }
108+
if let Both | Back = self.state { accum = self.b.fold(accum, &mut f); }
109+
accum
110+
}
111+
```
112+
113+
This version is both shorter and clearer.
114+
115+
With `while let`, the ergonomics and in particular the readability can be
116+
significantly improved.
117+
118+
The following snippet (written with this RFC):
119+
120+
```rust
121+
while let A(x) | B(x) = source() {
122+
react_to(x);
123+
}
124+
```
125+
126+
must currently be written as:
127+
128+
```rust
129+
loop {
130+
match source() {
131+
A(x) | B(x) => react_to(x),
132+
_ => { break; }
133+
}
134+
}
135+
```
136+
137+
Another major motivation of the RFC is consistency with `match`.
138+
139+
To keep `let` statements consistent with `if let`, and to enable the scenario
140+
exemplified by `ParameterKind` in the [motivation], these or-patterns are
141+
allowed at the top level of `let` statements.
142+
143+
In addition to the `ParameterKind` example, we can also consider
144+
`slice.binary_search(&x)`. If we are only interested in the `index` at where
145+
`x` is or would be, without any regard for if it was there or not, we can
146+
now simply write:
147+
148+
```rust
149+
let Ok(index) | Err(index) = slice.binary_search(&x);
150+
```
151+
152+
and we will get back the `index` in any case and continue on from there.
153+
154+
# Guide-level explanation
155+
[guide-level-explanation]: #guide-level-explanation
156+
157+
[RFC 2005]: https://github.com/rust-lang/rfcs/blob/master/text/2005-match-ergonomics.md#examples
158+
159+
[RFC 2005], in describing the third example in the section "Examples", refers to
160+
patterns with `|` in them as "or" patterns. This RFC adopts the same terminology.
161+
162+
While the "sum" of all patterns in `match` must be irrefutable, or in other
163+
words: cover all cases, be exhaustive, this is not the case (currently) with
164+
`if/while let`, which may have a refutable pattern.
165+
This RFC does not change this.
166+
167+
The RFC only extends the use of or-patterns at the top level from `match`es
168+
to `if let` and `while let` expressions as well as `let` statements.
169+
170+
For examples, see [motivation].
171+
172+
# Reference-level explanation
173+
[reference-level-explanation]: #reference-level-explanation
174+
175+
## Grammar
176+
177+
[§ 7.2.24]: https://doc.rust-lang.org/grammar.html#if-let-expressions
178+
[§ 7.2.25]: https://doc.rust-lang.org/grammar.html#while-let-loops
179+
180+
### `if let`
181+
182+
The grammar in [§ 7.2.24] is changed from:
183+
184+
```
185+
if_let_expr : "if" "let" pat '=' expr '{' block '}'
186+
else_tail ? ;
187+
```
188+
189+
to:
190+
191+
```
192+
if_let_expr : "if" "let" pat [ '|' pat ] * '=' expr '{' block '}'
193+
else_tail ? ;
194+
```
195+
196+
### `while let`
197+
198+
The grammar in [§ 7.2.25] is changed from:
199+
200+
```
201+
while_let_expr : [ lifetime ':' ] ? "while" "let" pat '=' expr '{' block '}' ;
202+
```
203+
204+
to:
205+
206+
```
207+
while_let_expr : [ lifetime ':' ] ? "while" "let" pat [ '|' pat ] * '=' expr '{' block '}' ;
208+
```
209+
210+
### `let` statements
211+
212+
The statement `stmt` grammar is replaced with a language equivalent to:
213+
214+
```
215+
stmt ::= old_stmt_grammar
216+
| let_stmt_many
217+
;
218+
219+
let_stmt_many ::= "let" pat_two_plus "=" expr ";"
220+
221+
pat_two_plus ::= pat [ '|' pat ] + ;
222+
```
223+
224+
## Syntax lowering
225+
226+
The changes proposed in this RFC with respect to `if let` and `while let`
227+
can be implemented by transforming the `if/while let` constructs with a
228+
syntax-lowering pass into `match` and `loop` + `match` expressions.
229+
230+
Meanwhile, `let` statements can be transformed into a continuation with
231+
`match` as described below.
232+
233+
### Examples, `if let`
234+
235+
[`if let` RFC]: https://github.com/rust-lang/rfcs/pull/160
236+
237+
These examples are extensions on the [`if let` RFC]. Therefore, the RFC avoids
238+
duplicating any details already specified there.
239+
240+
Source:
241+
```rust
242+
if let PAT [| PAT]* = EXPR { BODY }
243+
```
244+
Result:
245+
```rust
246+
match EXPR {
247+
PAT [| PAT]* => { BODY }
248+
_ => {}
249+
}
250+
```
251+
252+
Source:
253+
```rust
254+
if let PAT [| PAT]* = EXPR { BODY_IF } else { BODY_ELSE }
255+
```
256+
Result:
257+
```rust
258+
match EXPR {
259+
PAT [| PAT]* => { BODY_IF }
260+
_ => { BODY_ELSE }
261+
}
262+
```
263+
264+
Source:
265+
```rust
266+
if COND {
267+
BODY_IF
268+
} else if let PAT [| PAT]* = EXPR {
269+
BODY_ELSE_IF
270+
} else {
271+
BODY_ELSE
272+
}
273+
```
274+
Result:
275+
```rust
276+
if COND {
277+
BODY_IF
278+
} else {
279+
match EXPR {
280+
PAT [| PAT]* => { BODY_ELSE_IF }
281+
_ => { BODY_ELSE }
282+
}
283+
}
284+
```
285+
286+
Source
287+
```rust
288+
if let PAT [| PAT]* = EXPR {
289+
BODY_IF
290+
} else if COND {
291+
BODY_ELSE_IF_1
292+
} else if OTHER_COND {
293+
BODY_ELSE_IF_2
294+
}
295+
```
296+
Result:
297+
```rust
298+
match EXPR {
299+
PAT [| PAT]* => { BODY_IF }
300+
_ if COND => { BODY_ELSE_IF_1 }
301+
_ if OTHER_COND => { BODY_ELSE_IF_2 }
302+
_ => {}
303+
}
304+
```
305+
306+
### Examples, `while let`
307+
308+
[`while let` RFC]: https://github.com/rust-lang/rfcs/pull/214
309+
310+
The following example is an extension on the [`while let` RFC].
311+
312+
Source
313+
```rust
314+
['label:] while let PAT [| PAT]* = EXPR {
315+
BODY
316+
}
317+
```
318+
Result:
319+
```rust
320+
['label:] loop {
321+
match EXPR {
322+
PAT [| PAT]* => BODY,
323+
_ => break
324+
}
325+
}
326+
```
327+
328+
## Desugaring `let` statements with `|` in the top-level pattern
329+
330+
This is a possible desugaring that a Rust compiler may do.
331+
While such a compiler may elect to implement this differently,
332+
these semantics should be kept.
333+
334+
Source:
335+
```rust
336+
{
337+
// prefix of statements:
338+
stmt*
339+
// The let statement which is the cause for desugaring:
340+
let_stmt_many
341+
// the continuation / suffix of statements:
342+
stmt*
343+
tail_expr? // Meta-variable for optional tail expression without ; at end
344+
}
345+
```
346+
Result
347+
```rust
348+
{
349+
stmt*
350+
match expr {
351+
pat_two_plus => {
352+
stmt*
353+
tail_expr?
354+
}
355+
}
356+
}
357+
```
358+
359+
# Drawbacks
360+
[drawbacks]: #drawbacks
361+
362+
This adds more additions to the grammar and makes the compiler more complex.
363+
364+
# Rationale and alternatives
365+
[alternatives]: #alternatives
366+
367+
This could simply not be done.
368+
Consistency with `match` is however on its own reason enough to do this.
369+
370+
It could be claimed that the `if/while let` RFCs already mandate this RFC,
371+
this RFC does answer that question and instead simply mandates it now.
372+
373+
Another alternative is to only deal with `if/while let` expressions but not
374+
`let` statements.
375+
376+
# Unresolved questions
377+
[unresolved]: #unresolved-questions
378+
379+
The exact syntax transformations should be deferred to the implementation.
380+
This RFC does not mandate exactly how the AST:s should be transformed, only
381+
that the or-pattern feature be supported.
382+
383+
There are no unresolved questions.

0 commit comments

Comments
 (0)