|
| 1 | +- Feature Name: if_let_guard |
| 2 | +- Start Date: 2018-01-15 |
| 3 | +- RFC PR: |
| 4 | +- Rust Issue: |
| 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 | +The code is clearer with an `if let` guard as follows. |
| 29 | + |
| 30 | +# Guide-level explanation |
| 31 | +[guide-level-explanation]: #guide-level-explanation |
| 32 | + |
| 33 | +*(Adapted from Rust book)* |
| 34 | + |
| 35 | +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. |
| 36 | + |
| 37 | +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). |
| 38 | + |
| 39 | +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): |
| 40 | +```rust |
| 41 | +match ui.wait_event() { |
| 42 | + KeyPress(mod_, key, datum) if let Some(action) = intercept(mod_, key) => act(action, datum), |
| 43 | + ev => accept!(ev), |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +# Reference-level explanation |
| 48 | +[reference-level-explanation]: #reference-level-explanation |
| 49 | + |
| 50 | +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.) |
| 51 | + |
| 52 | +An arm may not have both an `if` and an `if let` guard. |
| 53 | + |
| 54 | +# Drawbacks |
| 55 | +[drawbacks]: #drawbacks |
| 56 | + |
| 57 | +* It further complicates the grammar. |
| 58 | +* It is ultimately syntactic sugar, but the transformation to present Rust is potentially non-obvious. |
| 59 | + |
| 60 | +# Rationale and alternatives |
| 61 | +[alternatives]: #alternatives |
| 62 | + |
| 63 | +* 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. |
| 64 | +* 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. |
| 65 | + |
| 66 | +# Unresolved questions |
| 67 | +[unresolved]: #unresolved-questions |
| 68 | + |
| 69 | +Questions in scope of this proposal: none yet known |
| 70 | + |
| 71 | +Questions out of scope: |
| 72 | + |
| 73 | +* 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. |
| 74 | +* 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