Skip to content

Commit b55dfb7

Browse files
committed
move mismatched bindings sections to open questions
1 parent e7955b0 commit b55dfb7

File tree

1 file changed

+51
-60
lines changed

1 file changed

+51
-60
lines changed

text/3637-guard-patterns.md

Lines changed: 51 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
55

66
# Summary
7+
78
[summary]: #summary
89

910
This RFC proposes to add a new kind of pattern, the **guard pattern.** Like match arm guards, guard patterns restrict another pattern to match only if an expression evaluates to `true`. The syntax for guard patterns, `pat if condition`, is compatible with match arm guard syntax, so existing guards can be superceded by guard patterns without breakage.
1011

1112
# Motivation
13+
1214
[motivation]: #motivation
1315

1416
Guard patterns, unlike match arm guards, can be nested within other patterns. In particular, guard patterns nested within or-patterns can depend on the branch of the or-pattern being matched. This has the potential to simplify certain match expressions, and also enables the use of guards in other places where refutable patterns are acceptable. Furthermore, by moving the guard condition closer to the bindings upon which it depends, pattern behavior can be made more local.
1517

1618
# Guide-level explanation
19+
1720
[guide-level-explanation]: #guide-level-explanation
1821

1922
Guard patterns allow you to write guard expressions to decide whether or not something should match anywhere you can use a pattern, not just at the top level of `match` arms.
@@ -60,6 +63,7 @@ This is a **guard pattern**. It matches a value if `pattern` (the pattern it wra
6063
For new users, guard patterns are better explained without reference to match arm guards. Instead, they can be explained by similar examples to the ones currently used for match arm guards, followed by an example showing that they can be nested within other patterns and used outside of match arms.
6164

6265
# Reference-level explanation
66+
6367
[reference-level-explanation]: #reference-level-explanation
6468

6569
## Supersession of Match Arm Guards
@@ -76,7 +80,7 @@ x @ A(..) if pred <=> (x @ A(..)) if pred
7680
A(..) | B(..) if pred <=> (A(..) | B(..)) if pred
7781
```
7882

79-
## Precedence Relative to `|`
83+
## Precedence Relative to `|`
8084

8185
Consider the following match expression:
8286

@@ -107,12 +111,12 @@ Therefore guard patterns appearing at the top level in those places must also be
107111
// Not allowed:
108112
let x if guard(x) = foo() {} else { loop {} }
109113
if let x if guard(x) = foo() {}
110-
while let x if guard(x) = foo() {}
114+
while let x if guard(x) = foo() {}
111115

112116
// Allowed:
113117
let (x if guard(x)) = foo() {} else { loop {} }
114118
if let (x if guard(x)) = foo() {}
115-
while let (x if guard(x)) = foo() {}
119+
while let (x if guard(x)) = foo() {}
116120
```
117121

118122
Therefore the syntax for patterns becomes
@@ -121,15 +125,16 @@ Therefore the syntax for patterns becomes
121125
> _Pattern_ :\
122126
> &nbsp;&nbsp; &nbsp;&nbsp; _PatternNoTopGuard_\
123127
> &nbsp;&nbsp; | _GuardPattern_
124-
>
128+
>
125129
> _PatternNoTopGuard_ :\
126-
> &nbsp;&nbsp; &nbsp;&nbsp; `|`<sup>?</sup> _PatternNoTopAlt_ ( `|` _PatternNoTopAlt_ )<sup>\*</sup>
130+
> &nbsp;&nbsp; &nbsp;&nbsp; `|`<sup>?</sup> _PatternNoTopAlt_ ( `|` _PatternNoTopAlt_ )<sup>\*</sup>
127131
128132
With `if let` and `while let` expressions now using `PatternNoTopGuard`. `let` statements and function parameters can continue to use `PatternNoTopAlt`.
129133

130134
## Bindings Available to Guards
131135

132136
The only bindings available to guard conditions are
137+
133138
- bindings from the scope containing the pattern match, if any; and
134139
- bindings introduced by identifier patterns _within_ the guard pattern.
135140

@@ -157,20 +162,6 @@ let (Struct { x, y } if x == y) = Struct { x: 0, y: 0 } else { /* ... */ }
157162

158163
In general, guards can, without changing meaning, "move outwards" until they reach an or-pattern where the condition can be different in other branches, and "move inwards" until they reach a level where the identifiers they reference are not bound.
159164

160-
## Bindings Must Still Match Across Disjunctions
161-
162-
This RFC does _not_ propose to change what bindings are allowed in disjunctions, even when those bindings are used only within guard patterns.
163-
164-
For example, the following code will error just like it would without any guard patterns:
165-
166-
```rust
167-
match Some(0) {
168-
Some(x if x > 0) | None => {},
169-
//~^ ERROR variable `x` is not bound in all patterns
170-
_ => {},
171-
}
172-
```
173-
174165
## As Macro Arguments
175166

176167
Currently, `if` is in the follow set of `pat` and `pat_param` fragments, so top-level guards cannot be used as arguments for the current edition. This is identical to the situation with top-level or-patterns as macro arguments, and guard patterns will take the same approach:
@@ -180,11 +171,13 @@ Currently, `if` is in the follow set of `pat` and `pat_param` fragments, so top-
180171
3. In the next edition, update `pat` fragments to accept `Pattern` once again.
181172

182173
# Drawbacks
174+
183175
[drawbacks]: #drawbacks
184176

185177
Rather than matching only by structural properties of ADTs, equality, and ranges of certain primitives, guards give patterns the power to express arbitrary restrictions on types. This necessarily makes patterns more complex both in implementation and in concept.
186178

187179
# Rationale and alternatives
180+
188181
[rationale-and-alternatives]: #rationale-and-alternatives
189182

190183
## "Or-of-guards" Patterns
@@ -203,7 +196,7 @@ Therefore, we could choose to restrict guard patterns so that they appear only i
203196
This RFC refers to this as "or-of-guards" patterns, because it changes or-patterns from or-ing together a list of patterns to or-ing together a list of optionally guarded patterns.
204197

205198
Note that, currently, most patterns are actually parsed as an or-pattern with only one choice.
206-
Therefore, to achieve the effect of forcing patterns as far out as possible guards would only be allowed in or-patterns with more than one choice.
199+
Therefore, to achieve the effect of forcing patterns as far out as possible guards would only be allowed in or-patterns with more than one choice.
207200

208201
There are, however, a couple reasons where it could be desirable to allow guards further inwards than strictly necessary.
209202

@@ -245,6 +238,7 @@ match order {
245238
### Pattern Macros
246239

247240
If guards can only appear immediately within or-patterns, then either
241+
248242
- pattern macros can emit guards at the top-level, in which case they can only be called immediately within or-patterns without risking breakage if the macro definition changes (even to another valid pattern!); or
249243
- pattern macros cannot emit guards at the top-level, forcing macro authors to use terrible workarounds like `(Some(x) if guard(x)) | (Some(x) if false)` if they want to use the feature.
250244

@@ -254,16 +248,15 @@ This can also be seen as a special case of the previous argument, as pattern mac
254248

255249
It may seem odd that we explicitly require const patterns to use pure `PartialEq` implementations (and the upcoming [proposal](https://hackmd.io/4qDDMcvyQ-GDB089IPcHGg) for deref patterns to use pure `Deref` implementations), but allow arbitrary side effects in guards. The ultimate reason for this is that, unlike const patterns and the proposed deref patterns, guard patterns are always refutable.
256250

257-
258251
Without the requirement of `StructuralPartialEq` we could write a `PartialEq` implementation which always returns `false`, resulting either in UB or a failure to ensure match exhaustiveness:
259252

260253
```rust
261254
const FALSE: EvilBool = EvilBool(false);
262-
const TRUE: EvilBool = EvilBool(true);
255+
const TRUE: EvilBool = EvilBool(true);
263256

264257
match EvilBool(false) {
265258
FALSE => {},
266-
TRUE => {},
259+
TRUE => {},
267260
}
268261
```
269262

@@ -289,45 +282,20 @@ match EvilBool(false) {
289282
But this will always be a compilation error because the `match` statement is no longer assumed to be exhaustive.
290283

291284
# Prior art
285+
292286
[prior-art]: #prior-art
293287

294-
This feature has been implemented in the [Unison](https://www.unison-lang.org/docs/language-reference/guard-patterns/), [Wolfram](https://reference.wolfram.com/language/ref/Condition.html), and [E ](https://en.wikipedia.org/wiki/E_(programming_language)) languages.
288+
This feature has been implemented in the [Unison](https://www.unison-lang.org/docs/language-reference/guard-patterns/), [Wolfram](https://reference.wolfram.com/language/ref/Condition.html), and [E ](<https://en.wikipedia.org/wiki/E_(programming_language)>) languages.
295289

296290
Guard patterns are also very similar to Haskell's [view patterns](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/view_patterns.html), which are more powerful and closer to a hypothetical "`if let` pattern" than a guard pattern as this RFC proposes it.
297291

298292
# Unresolved questions
299-
[unresolved-questions]: #unresolved-questions
300-
301-
- How should we refer to this feature?
302-
- "Guard pattern" will likely be most intuitive to users already familiar with match arm guards. Most likely, this includes anyone reading this, which is why this RFC uses that term.
303-
- "`if`-pattern" agrees with the naming of or-patterns, and obviously matches the syntax well. This is probably the most intuitive name for new users learning the feature.
304-
- Some other possibilities: "condition/conditioned pattern," "refinement/refined pattern," "restriction/restricted pattern," or "predicate/predicated pattern."
305-
- What anti-patterns should we lint against?
306-
- Using guard patterns to test equality or range membership when a literal or range pattern could be used instead?
307-
- Using guard patterns at the top-level of `if let` or `while let` instead of let chains?
308-
- Guard patterns within guard patterns instead of using one guard with `&&` in the condition?
309-
- `foo @ (x if guard(x))` rather than `(foo @ x) if guard(x)`? Or maybe this is valid in some cases for localizing match behavior?
310-
- Is `pat_no_top_guard` a good name, or should we use something shorter like `pat_unguarded`?
311-
312-
# Future possibilities
313-
[future-possibilities]: #future-possibilities
314-
315-
## Allowing `if let`
316-
317-
Users expect to be able to write `if let` where they can write `if`. Allowing this in guard patterns would make them significantly more powerful, but also more complex.
318-
319-
One way to think about this is that patterns serve two functions:
320293

321-
1. Refinement: refutable patterns only match some subset of a type's values.
322-
2. Destructuring: patterns use the structure common to values of that subset to extract data.
323-
324-
Guard patterns as described here provide _arbitrary refinement_. That is, guard patterns can match based on whether any arbitrary expression evaluates to true.
325-
326-
Allowing `if let` allows not just arbitrary refinement, but also _arbitrary destructuring_. The value(s) bound by an `if let` pattern can depend on the value of an arbitrary expression.
294+
[unresolved-questions]: #unresolved-questions
327295

328296
## Allowing Mismatching Bindings When Possible
329297

330-
Users will likely want to write something like
298+
Ideally, users would be able to write something to the effect of
331299

332300
```rust
333301
match Some(0) {
@@ -337,19 +305,42 @@ match Some(0) {
337305
```
338306

339307
This is also very useful for macros, because it allows
308+
340309
1. pattern macros to use guard patterns freely without introducing new bindings the user has to be aware of in order to use the pattern macro within a disjunction, and
341310
2. macro users to pass guard patterns to macros freely, even if the macro uses the pattern within a disjunction.
342311

343312
As mentioned above, this case is not covered by this RFC, because `x` would need to be bound in both cases of the disjunction.
344313

345-
However, we could support this by automatically detecting that `x` is not ever used outside of the guard pattern, and allowing the guard to capture the binding, so it wouldn't have to be bound in other cases of the disjunction.
314+
### Possible Design
346315

347-
We could also make this capturing behavior explicit, with some kind of syntax extending guard patterns:
316+
[@tmandry proposed](https://github.com/rust-lang/rfcs/pull/3637#issuecomment-2307839511) amending the rules for how names can be bound in patterns to the following:
348317

349-
```rust
350-
// example syntax by analogy with closures
351-
// probably not what we'd want to go with, since you can't specify which bindings are captured
352-
Some(x move if x > 0) | None
353-
```
318+
1. Unchanged: If a name is bound in any part of a pattern, it shadows existing definitions of the name.
319+
2. Unchanged: If a name bound by a pattern is used in the body, it must be defined in every part of a disjunction and be the same type in each.
320+
3. Removed: ~~Bindings introduced in one branch of a disjunction must be introduced in all branches.~~
321+
4. Added: If a name is bound in multiple parts of a disjunction, it must be bound to the same type in every part. (Enforced today by the combination of 2 and 3.)
322+
323+
## How to Refer to Guard Patterns
324+
325+
Some possibilities:
326+
327+
- "Guard pattern" will likely be most intuitive to users already familiar with match arm guards. Most likely, this includes anyone reading this, which is why this RFC uses that term.
328+
- "`if`-pattern" agrees with the naming of or-patterns, and obviously matches the syntax well. This is probably the most intuitive name for new users learning the feature.
329+
- Some other possibilities: "condition/conditioned pattern," "refinement/refined pattern," "restriction/restricted pattern," or "predicate/predicated pattern."
330+
331+
[future-possibilities]: #future-possibilities
332+
333+
# Future Possibilities
334+
335+
## Allowing `if let`
336+
337+
Users expect to be able to write `if let` where they can write `if`. Allowing this in guard patterns would make them significantly more powerful, but also more complex.
338+
339+
One way to think about this is that patterns serve two functions:
340+
341+
1. Refinement: refutable patterns only match some subset of a type's values.
342+
2. Destructuring: patterns use the structure common to values of that subset to extract data.
354343

355-
This would also give the guard ownership of the bound value, which may be desirable in other cases.
344+
Guard patterns as described here provide _arbitrary refinement_. That is, guard patterns can match based on whether any arbitrary expression evaluates to true.
345+
346+
Allowing `if let` allows not just arbitrary refinement, but also _arbitrary destructuring_. The value(s) bound by an `if let` pattern can depend on the value of an arbitrary expression.

0 commit comments

Comments
 (0)