From cb5a69b69c3da10f134c13701bd225df80a37103 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Tue, 14 May 2024 23:59:14 -0700 Subject: [PATCH 01/18] add first draft of guard-patterns --- text/0000-guard-patterns.md | 248 ++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 text/0000-guard-patterns.md diff --git a/text/0000-guard-patterns.md b/text/0000-guard-patterns.md new file mode 100644 index 00000000000..6d01a45471a --- /dev/null +++ b/text/0000-guard-patterns.md @@ -0,0 +1,248 @@ +- Feature Name: `guard_patterns` +- Start Date: 2024-05-13 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +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. + +# Motivation +[motivation]: #motivation + +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. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +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. + +For example, imagine that you're writing a function that decides whether a user has enough credit to buy an item. Regular users have to pay 100 credits, but premium subscribers get a 20% discount. You could implement this with a match expression as follows: + +```rust +match user.subscription_plan() { + Plan::Regular if user.credit() >= 100 => { + // complete the transaction + } + Plan::Premium if user.credit() >= 80 => { + // complete the transaction + } + _ => { + // the user doesn't have enough credit, return an error message + } +} +``` + +But this isn't great, because two of the match arms have exactly the same body. Instead, we can write + +```rust +match user.subscription_plan() { + (Plan::Regular if user.credit() >= 100) | (Plan::Premium if user.credit() >= 80) => { + // complete the transaction + } + _ => { + // the user doesn't have enough credit, return an error message + } +} +``` + +Now we have just one arm for a successful transaction, with an or-pattern combining the two arms we used to have. The patterns two nested patterns are of the form + +```rust +pattern if expr +``` + +This is a **guard pattern**. It matches a value if `pattern` (the pattern it wraps) matches that value, _and_ `expr` evaluates to `true`. Like in match arm guards, `expr` can use values bound in `pattern`. + +## For New Users + +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. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## Supersession of Match Arm Guards + +Rather than being parsed as part of the match expression, guards in match arms will instead be parsed as a guard pattern. For this reason, the `if` pattern operator must have lower precedence than all other pattern operators. + +That is, + +```rs +// Let <=> denote equivalence of patterns. + +x @ A(..) if pred <=> (x @ A(..)) if pred +&A(..) if pred <=> (&A(..)) if pred +A(..) | B(..) if pred <=> (A(..) | B(..)) if pred +``` + +## Interaction with Expression Operators + +The or-pattern operator and the bitwise OR operator both use the `|` token. This creates a parsing ambiguity: + +```rust +// How is this parsed? +false if foo | true +// As a guard pattern nested in an or-pattern? +(false if foo) | true +// Or as a pattern guarded by a bitwise OR operation? +false if (foo | true) +``` + +For that reason, guard patterns nested within or-patterns must be explicitly parenthesized. + +There's a similar ambiguity between `=` used as the assignment operator within the guard +and used outside to indicate assignment to the pattern (e.g. in `if`-`let`, `while let`, etc.). +Therefore guard patterns appearing at the top level in those places must also be parenthesized: + +```rust +while let x if guard(x) = foo() {} // not allowed +while let (x if guard(x)) = foo() {} // allowed +``` + +Therefore the syntax for patterns becomes + +> **Syntax**\ +> _Pattern_ :\ +>       _PatternNoTopGuard_\ +>    | _GuardPattern_ +> +> _PatternNoTopGuard_ :\ +>       `|`? _PatternNoTopAlt_ ( `|` _PatternNoTopAlt_ )\* + +With `if let`, `while let`, and `for` expressions now using `PatternNoTopGuard`. `let` statements can continue to use `PatternNoTopAlt`. + +## Bindings Available to Guards + +The only bindings available to guard conditions are +- bindings from the scope containing the pattern match, if any; and +- bindings introduced by identifier patterns _within_ the guard pattern. + +This disallows, for example, the following uses: + +```rust +// ERROR: `x` bound outside the guard pattern +let (x, y if x == y) = (0, 0) else { /* ... */ } +let [x, y if x == y] = [0, 0] else { /* ... */ } +let TupleStruct(x, y if x == y) = TupleStruct(0, 0) else { /* ... */ } +let Struct { x, y: y if x == y } = Struct { x: 0, y: 0 } else { /* ... */ } + +// ERROR: `x` cannot be used by other parameters' patterns +fn function(x: usize, ((y if x == y, _) | (_, y)): (usize, usize)) { /* ... */ } +``` + +Note that in each of these cases besides the function, the condition is still possible by moving the condition outside of the destructuring pattern: + +```rust +let ((x, y) if x == y) = (0, 0) else { /* ... */ } +let ([x, y] if x == y) = [0, 0] else { /* ... */ } +let (TupleStruct(x, y) if x == y) = TupleStruct(0, 0) else { /* ... */ } +let (Struct { x, y } if x == y) = Struct { x: 0, y: 0 } else { /* ... */ } +``` + +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. + +# Drawbacks +[drawbacks]: #drawbacks + +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. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +## "Or-of-guards" Patterns + +Earlier it was mentioned that guards can "move outwards" up to an or-pattern without changing meaning: + +```rust + (Ok(Ok(x if x > 0))) | (Err(Err(x if x < 0))) +<=> (Ok(Ok(x) if x > 0)) | (Err(Err(x) if x < 0)) +<=> (Ok(Ok(x)) if x > 0) | (Err(Err(x)) if x < 0) +// cannot move outwards any further, because the conditions are different +``` + +In most situations, it is preferable to have the guard as far outwards as possible; that is, at the top-level of the whole pattern or immediately within one alternative of an or-pattern. +Therefore, we could choose to restrict guard patterns so that they appear only in these places. +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. + +Note that, currently, most patterns are actually parsed as an or-pattern with only one choice. +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. + +There are, however, a couple reasons where it could be desirable to allow guards further inwards than strictly necessary. + +### Localization of Behavior + +Sometimes guards are only related to information from a small part of a large structure being matched. + +For example, consider a function that iterates over a list of customer orders and performs different actions depending on the customer's subscription plan, the item type, the payment info, and various other factors: + +```rust +match order { + Order { + customer: customer if customer.subscription_plan() == Plan::Premium, + payment: Payment::Cash(amount) if amount.in_usd() > 100, + item_type: ItemType::A, + // a bunch of other conditions... + } => { /* ... */ } + // other similar branches... +} +``` + +Here, the pattern `customer if customer.subscription_plan() == Plan::Premium` has a clear meaning: it matches customers with premium subscriptions. All of the behavior of the pattern pertaining to the customer is in one place. However, if we move the guard outwards to wrap the entire order, the behavior is spread out and much harder to understand -- particularly if other its merged with conditions for other parts of the order: + +```rust +// The same match statement using or-of-guards. +match order { + Order { + customer, + payment: Payment::Cash(amount), + item_type: ItemType::A, + // a bunch of other conditions... + } if customer.subscription_plan() == Plan::Premium && amount.in_usd() > 100 => { /* ... */ } + // other similar branches... +} +``` + +### Pattern Macros + +If guards can only appear immediately within or-patterns, then either +- 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 +- 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. + +This can also be seen as a special case of the previous argument, as pattern macros fundamentally assume that patterns can be built out of composable, local pieces. + +# Prior art +[prior-art]: #prior-art + +As far as I am aware, this feature has not been implemented in any other programming languages. + +Guard patterns are, however, 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. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- How should we refer to this feature? + - "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. + - "`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. + - Some other possibilities: "condition/conditioned pattern," "refinement/refined pattern," "restriction/restricted pattern," or "predicate/predicated pattern." +- What anti-patterns should we lint against? + - Using guard patterns to test equality or range membership when a literal or range pattern could be used instead? + - Using guard patterns at the top-level of `if let` or `while let` instead of let chains? + - Guard patterns within guard patterns instead of using one guard with `&&` in the condition? + - `foo @ (x if guard(x))` rather than `(foo @ x) if guard(x)`? Or maybe this is valid in some cases for localizing match behavior? + +# Future possibilities +[future-possibilities]: #future-possibilities + +## Allowing `if let` + +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. + +One way to think about this is that patterns serve two functions: + +1. Refinement: refutable patterns only match some subset of a type's values. +2. Destructuring: patterns use the structure common to values of that subset to extract data. + +Guard patterns as described here provide _arbitrary refinement_. That is, guard patterns can match based on whether any arbitrary expression evaluates to true. + +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. From 55acb0b265926e59bfbad19d74715f7d5589fd10 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Wed, 15 May 2024 00:09:32 -0700 Subject: [PATCH 02/18] add guard-patterns pr number --- text/{0000-guard-patterns.md => 3637-guard-patterns.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-guard-patterns.md => 3637-guard-patterns.md} (99%) diff --git a/text/0000-guard-patterns.md b/text/3637-guard-patterns.md similarity index 99% rename from text/0000-guard-patterns.md rename to text/3637-guard-patterns.md index 6d01a45471a..9485861bb34 100644 --- a/text/0000-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -1,6 +1,6 @@ - Feature Name: `guard_patterns` - Start Date: 2024-05-13 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3637](https://github.com/rust-lang/rfcs/pull/3637) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary From 64a234a7fa1b24fed53750eb3005df11ece4db0f Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Wed, 15 May 2024 01:04:06 -0700 Subject: [PATCH 03/18] fix typos --- text/3637-guard-patterns.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 9485861bb34..ab2f76350d7 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -47,7 +47,7 @@ match user.subscription_plan() { } ``` -Now we have just one arm for a successful transaction, with an or-pattern combining the two arms we used to have. The patterns two nested patterns are of the form +Now we have just one arm for a successful transaction, with an or-pattern combining the two arms we used to have. The two nested patterns are of the form ```rust pattern if expr @@ -188,7 +188,7 @@ match order { } ``` -Here, the pattern `customer if customer.subscription_plan() == Plan::Premium` has a clear meaning: it matches customers with premium subscriptions. All of the behavior of the pattern pertaining to the customer is in one place. However, if we move the guard outwards to wrap the entire order, the behavior is spread out and much harder to understand -- particularly if other its merged with conditions for other parts of the order: +Here, the pattern `customer if customer.subscription_plan() == Plan::Premium` has a clear meaning: it matches customers with premium subscriptions. All of the behavior of the pattern pertaining to the customer is in one place. However, if we move the guard outwards to wrap the entire order, the behavior is spread out and much harder to understand -- particularly if it is merged with conditions for other parts of the order struct: ```rust // The same match statement using or-of-guards. From a84f01b0e2b16fbc1dc87fc2f70963983e09d387 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Wed, 15 May 2024 01:16:56 -0700 Subject: [PATCH 04/18] further explain behavior localization example --- text/3637-guard-patterns.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index ab2f76350d7..724b1c1c81f 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -179,8 +179,10 @@ For example, consider a function that iterates over a list of customer orders an ```rust match order { Order { + // These patterns match based on method calls, necessitating the use of a guard pattern: customer: customer if customer.subscription_plan() == Plan::Premium, payment: Payment::Cash(amount) if amount.in_usd() > 100, + item_type: ItemType::A, // a bunch of other conditions... } => { /* ... */ } @@ -188,7 +190,7 @@ match order { } ``` -Here, the pattern `customer if customer.subscription_plan() == Plan::Premium` has a clear meaning: it matches customers with premium subscriptions. All of the behavior of the pattern pertaining to the customer is in one place. However, if we move the guard outwards to wrap the entire order, the behavior is spread out and much harder to understand -- particularly if it is merged with conditions for other parts of the order struct: +Here, the pattern `customer if customer.subscription_plan() == Plan::Premium` has a clear meaning: it matches customers with premium subscriptions. Similarly, `Payment::Cash(amount) if amount.in_usd() > 100` matches cash payments of amounts greater than 100USD. All of the behavior of the pattern pertaining to the customer is in one place, and all behavior pertaining to the payment is in another. However, if we move the guard outwards to wrap the entire order struct, the behavior is spread out and much harder to understand -- particularly if the two conditions are merged into one: ```rust // The same match statement using or-of-guards. From be09503e9c43dad9f15137b26ecd426a2f92f46d Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Wed, 15 May 2024 23:08:06 -0700 Subject: [PATCH 05/18] clarify precedence of `if` relative to `|` --- text/3637-guard-patterns.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 724b1c1c81f..9136c1763b7 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -90,6 +90,7 @@ false if (foo | true) ``` For that reason, guard patterns nested within or-patterns must be explicitly parenthesized. +Otherwise, the `|` will be parsed as a bitwise OR to maintain backwards compatability with match arm guards. There's a similar ambiguity between `=` used as the assignment operator within the guard and used outside to indicate assignment to the pattern (e.g. in `if`-`let`, `while let`, etc.). From 8b63437ca489512ac284e8229ac25b7314ce987f Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Thu, 16 May 2024 19:57:00 -0700 Subject: [PATCH 06/18] rewrite parsing section for clarity --- text/3637-guard-patterns.md | 44 ++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 9136c1763b7..11bcadf04e8 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -76,29 +76,43 @@ x @ A(..) if pred <=> (x @ A(..)) if pred A(..) | B(..) if pred <=> (A(..) | B(..)) if pred ``` -## Interaction with Expression Operators +## Precedence Relative to `|` -The or-pattern operator and the bitwise OR operator both use the `|` token. This creates a parsing ambiguity: +Consider the following match expression: ```rust -// How is this parsed? -false if foo | true -// As a guard pattern nested in an or-pattern? -(false if foo) | true -// Or as a pattern guarded by a bitwise OR operation? -false if (foo | true) +match foo { + A | B if c | d => {}, +} +``` + +This match arm is currently parsed as `(A | B) if (c | d)`, with the first `|` being the or-operator on patterns and the second being the bitwise OR operator on expressions. Therefore, to maintain backwards compatability, `if` must have lower precedence than `|` on both sides (or equivalently, for both meanings of `|`). For that reason, guard patterns nested within or-patterns must be explicitly parenthesized: + +```rust +// This is not an or-pattern of guards: + a if b | c if d +<=> (a if (b | c)) if d + +// Instead, write +(a if b) | (c if d) ``` -For that reason, guard patterns nested within or-patterns must be explicitly parenthesized. -Otherwise, the `|` will be parsed as a bitwise OR to maintain backwards compatability with match arm guards. +## In Assignment-Like Contexts -There's a similar ambiguity between `=` used as the assignment operator within the guard -and used outside to indicate assignment to the pattern (e.g. in `if`-`let`, `while let`, etc.). +There's an ambiguity between `=` used as the assignment operator within the guard +and used outside to indicate assignment to the pattern (e.g. in `if let`) Therefore guard patterns appearing at the top level in those places must also be parenthesized: ```rust -while let x if guard(x) = foo() {} // not allowed -while let (x if guard(x)) = foo() {} // allowed +// Not allowed: +let x if guard(x) = foo() {} +if let x if guard(x) = foo() {} +while let x if guard(x) = foo() {} + +// allowed +let (x if guard(x)) = foo() {} // Note that this would still error after parsing, since guard patterns are always refutable. +if let (x if guard(x)) = foo() {} +while let (x if guard(x)) = foo() {} ``` Therefore the syntax for patterns becomes @@ -111,7 +125,7 @@ Therefore the syntax for patterns becomes > _PatternNoTopGuard_ :\ >       `|`? _PatternNoTopAlt_ ( `|` _PatternNoTopAlt_ )\* -With `if let`, `while let`, and `for` expressions now using `PatternNoTopGuard`. `let` statements can continue to use `PatternNoTopAlt`. +With `if let` and `while let` expressions now using `PatternNoTopGuard`. `let` statements and function parameters can continue to use `PatternNoTopAlt`. ## Bindings Available to Guards From 2080f874e29567c36cd91f92b456488fd06eff8c Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Thu, 16 May 2024 20:14:11 -0700 Subject: [PATCH 07/18] add section on guard patterns as macro arguments --- text/3637-guard-patterns.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 11bcadf04e8..b840e520787 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -157,6 +157,14 @@ let (Struct { x, y } if x == y) = Struct { x: 0, y: 0 } else { /* ... */ } 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. +## As Macro Arguments + +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: + +1. Update `pat` fragments to accept `PatternNoTopGuard` rather than `Pattern`. +2. Introduce a new fragment specifier, `pat_no_top_guard`, which works in all editions and accepts `PatternNoTopGuard`. +3. In the next edition, update `pat` fragments to accept `Pattern` once again. + # Drawbacks [drawbacks]: #drawbacks @@ -247,6 +255,7 @@ Guard patterns are, however, very similar to Haskell's [view patterns](https://g - Using guard patterns at the top-level of `if let` or `while let` instead of let chains? - Guard patterns within guard patterns instead of using one guard with `&&` in the condition? - `foo @ (x if guard(x))` rather than `(foo @ x) if guard(x)`? Or maybe this is valid in some cases for localizing match behavior? +- Is `pat_no_top_guard` a good name, or should we use something shorter like `pat_unguarded`? # Future possibilities [future-possibilities]: #future-possibilities From 452d9d0cc8714e9b4f4c1e52421a9fede6365ea3 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Fri, 24 May 2024 22:13:52 -0700 Subject: [PATCH 08/18] add E language as prior art --- text/3637-guard-patterns.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index b840e520787..b137527a257 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -239,9 +239,9 @@ This can also be seen as a special case of the previous argument, as pattern mac # Prior art [prior-art]: #prior-art -As far as I am aware, this feature has not been implemented in any other programming languages. +This feature has been implemented in the [E programming language](https://en.wikipedia.org/wiki/E_(programming_language)) under the name ["such-that patterns"](http://erights.org/elang/grammar/patterns.html#suchThat). -Guard patterns are, however, 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. +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. # Unresolved questions [unresolved-questions]: #unresolved-questions From 5e826a04f797be8d68d5a28ea24ab794498d8922 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Fri, 24 May 2024 22:20:56 -0700 Subject: [PATCH 09/18] fix capitalization inconsistincies in code comments --- text/3637-guard-patterns.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index b137527a257..cf81a19d529 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -23,13 +23,13 @@ For example, imagine that you're writing a function that decides whether a user ```rust match user.subscription_plan() { Plan::Regular if user.credit() >= 100 => { - // complete the transaction + // Complete the transaction. } Plan::Premium if user.credit() >= 80 => { - // complete the transaction + // Complete the transaction. } _ => { - // the user doesn't have enough credit, return an error message + // The user doesn't have enough credit, return an error message. } } ``` @@ -39,10 +39,10 @@ But this isn't great, because two of the match arms have exactly the same body. ```rust match user.subscription_plan() { (Plan::Regular if user.credit() >= 100) | (Plan::Premium if user.credit() >= 80) => { - // complete the transaction + // Complete the transaction. } _ => { - // the user doesn't have enough credit, return an error message + // The user doesn't have enough credit, return an error message. } } ``` @@ -109,7 +109,7 @@ let x if guard(x) = foo() {} if let x if guard(x) = foo() {} while let x if guard(x) = foo() {} -// allowed +// Allowed: let (x if guard(x)) = foo() {} // Note that this would still error after parsing, since guard patterns are always refutable. if let (x if guard(x)) = foo() {} while let (x if guard(x)) = foo() {} @@ -181,7 +181,7 @@ Earlier it was mentioned that guards can "move outwards" up to an or-pattern wit (Ok(Ok(x if x > 0))) | (Err(Err(x if x < 0))) <=> (Ok(Ok(x) if x > 0)) | (Err(Err(x) if x < 0)) <=> (Ok(Ok(x)) if x > 0) | (Err(Err(x)) if x < 0) -// cannot move outwards any further, because the conditions are different +// Cannot move outwards any further, because the conditions are different. ``` In most situations, it is preferable to have the guard as far outwards as possible; that is, at the top-level of the whole pattern or immediately within one alternative of an or-pattern. @@ -207,9 +207,9 @@ match order { payment: Payment::Cash(amount) if amount.in_usd() > 100, item_type: ItemType::A, - // a bunch of other conditions... + // A bunch of other conditions... } => { /* ... */ } - // other similar branches... + // Other similar branches... } ``` @@ -222,9 +222,9 @@ match order { customer, payment: Payment::Cash(amount), item_type: ItemType::A, - // a bunch of other conditions... + // A bunch of other conditions... } if customer.subscription_plan() == Plan::Premium && amount.in_usd() > 100 => { /* ... */ } - // other similar branches... + // Other similar branches... } ``` From 486ac8a5201aa050d627af18cc3d6ad659771df0 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Mon, 17 Jun 2024 14:21:41 -0700 Subject: [PATCH 10/18] use valid let binding as example for parsing in assignment-like contexts --- text/3637-guard-patterns.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index cf81a19d529..4db1cb27c2c 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -105,12 +105,12 @@ Therefore guard patterns appearing at the top level in those places must also be ```rust // Not allowed: -let x if guard(x) = foo() {} +let x if guard(x) = foo() {} else { loop {} } if let x if guard(x) = foo() {} while let x if guard(x) = foo() {} // Allowed: -let (x if guard(x)) = foo() {} // Note that this would still error after parsing, since guard patterns are always refutable. +let (x if guard(x)) = foo() {} else { loop {} } if let (x if guard(x)) = foo() {} while let (x if guard(x)) = foo() {} ``` From 0581da516ec56d1c29155f32262c44d7ae86e11d Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Sat, 22 Jun 2024 13:29:22 -0700 Subject: [PATCH 11/18] add Unison and Wolfram langs as prior art --- text/3637-guard-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 4db1cb27c2c..ab0e0de8200 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -239,7 +239,7 @@ This can also be seen as a special case of the previous argument, as pattern mac # Prior art [prior-art]: #prior-art -This feature has been implemented in the [E programming language](https://en.wikipedia.org/wiki/E_(programming_language)) under the name ["such-that patterns"](http://erights.org/elang/grammar/patterns.html#suchThat). +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. 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. From 50157c7e17d355ee52c6a9b24844b6a5477bd8a2 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Thu, 15 Aug 2024 14:37:45 -0700 Subject: [PATCH 12/18] clarify no intent to change binding requirements wrt disjunctions --- text/3637-guard-patterns.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index ab0e0de8200..1c162700119 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -157,6 +157,20 @@ let (Struct { x, y } if x == y) = Struct { x: 0, y: 0 } else { /* ... */ } 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. +## Bindings Must Still Match Across Disjunctions + +This RFC does _not_ propose to change what bindings are allowed in disjunctions, even when those bindings are used only within guard patterns. + +For example, the following code will error just like it would without any guard patterns: + +```rust +match Some(0) { + Some(x if x > 0) | None => {}, + //~^ ERROR variable `x` is not bound in all patterns + _ => {}, +} +``` + ## As Macro Arguments 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: From e8c51c0c2a9165d9aa546117a3ce15a3fcac986f Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Thu, 15 Aug 2024 14:53:36 -0700 Subject: [PATCH 13/18] add future possibility of allowing captured bindings --- text/3637-guard-patterns.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 1c162700119..1948445f943 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -286,3 +286,28 @@ One way to think about this is that patterns serve two functions: Guard patterns as described here provide _arbitrary refinement_. That is, guard patterns can match based on whether any arbitrary expression evaluates to true. 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. + +## Allowing Mismatching Bindings When Possible + +Users will likely want to write something like + +```rust +match Some(0) { + Some(x if x > 0) | None => {}, + _ => {} +} +``` + +As mentioned above, this case is not covered by this RFC, because `x` would need to be bound in both cases of the disjunction. + +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. + +We could also make this capturing behavior explicit, with some kind of syntax extending guard patterns: + +```rust +// example syntax by analogy with closures +// probably not what we'd want to go with, since you can't specify which bindings are captured +Some(x move if x > 0) | None +``` + +This would also give the guard ownership of the bound value, which may be desirable in other cases. From 0765c1693cb9729493fc36b3825359a9ba62166d Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Thu, 22 Aug 2024 14:14:08 -0700 Subject: [PATCH 14/18] clarify why deref! and const patterns must be pure, but not guards --- text/3637-guard-patterns.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 1948445f943..0827b48aaba 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -250,6 +250,42 @@ If guards can only appear immediately within or-patterns, then either This can also be seen as a special case of the previous argument, as pattern macros fundamentally assume that patterns can be built out of composable, local pieces. +## Deref and Const Patterns Must Be Pure, But Not Guards + +It may seem odd that we explicitly require `deref!` and const patterns to use pure `Deref` and `PartialEq` implementations, respectively, but allow arbitrary side effects in guards. The ultimate reason for this is that, unlike `deref!` and const patterns, guard patterns are always refutable. + +With `deref!` patterns, we can write an impure `Deref` impl which alternates between returning `true` or `false` to get UB: +```rust +match EvilBox::new(false) { + deref!(true) => {} // Here the `EvilBox` dereferences to `false`. + deref!(false) => {} // And here to `true`. +} +``` + +And similarly, without the requirement of `StructuralPartialEq` we could write a `PartialEq` implementation which always returns `false`: + +```rust +const FALSE: EvilBool = EvilBool(false); +const TRUE: EvilBool = EvilBool(true); + +match EvilBool(false) { + FALSE => {}, + TRUE => {}, +} +``` + +However, this is not a problem with guard patterns because they already need a irrefutable alternative anyway. +For example, we could rewrite the const pattern example with guard patterns as follows: + +```rust +match EvilBool(false) { + x if x == FALSE => {}, + x if x == TRUE => {}, +} +``` + +But this will always be a compilation error because the `match` statement is no longer assumed to be exhaustive. + # Prior art [prior-art]: #prior-art From 3ec08d46f307767312d5453a6c8a6dad1916ff55 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Thu, 22 Aug 2024 14:19:46 -0700 Subject: [PATCH 15/18] add short paragraph on macro use case for mismatched bindings --- text/3637-guard-patterns.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 0827b48aaba..d5468c36b75 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -334,6 +334,10 @@ match Some(0) { } ``` +This is also very useful for macros, because it allows +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 +2. macro users to pass guard patterns to macros freely, even if the macro uses the pattern within a disjunction. + As mentioned above, this case is not covered by this RFC, because `x` would need to be bound in both cases of the disjunction. 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. From e7955b0a51e89c7701e106884572d5410dd2ec36 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Thu, 22 Aug 2024 16:43:13 -0700 Subject: [PATCH 16/18] emphasize that deref patterns are only a proposal, and fix typo --- text/3637-guard-patterns.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index d5468c36b75..0a29354b227 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -252,17 +252,10 @@ This can also be seen as a special case of the previous argument, as pattern mac ## Deref and Const Patterns Must Be Pure, But Not Guards -It may seem odd that we explicitly require `deref!` and const patterns to use pure `Deref` and `PartialEq` implementations, respectively, but allow arbitrary side effects in guards. The ultimate reason for this is that, unlike `deref!` and const patterns, guard patterns are always refutable. +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. -With `deref!` patterns, we can write an impure `Deref` impl which alternates between returning `true` or `false` to get UB: -```rust -match EvilBox::new(false) { - deref!(true) => {} // Here the `EvilBox` dereferences to `false`. - deref!(false) => {} // And here to `true`. -} -``` -And similarly, without the requirement of `StructuralPartialEq` we could write a `PartialEq` implementation which always returns `false`: +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: ```rust const FALSE: EvilBool = EvilBool(false); @@ -274,7 +267,16 @@ match EvilBool(false) { } ``` -However, this is not a problem with guard patterns because they already need a irrefutable alternative anyway. +And similarly, with an impure version of the proposed deref patterns, we could write a `Deref` impl which alternates between returning `true` or `false` to get UB: + +```rust +match EvilBox::new(false) { + deref!(true) => {} // Here the `EvilBox` dereferences to `false`. + deref!(false) => {} // And here to `true`. +} +``` + +However, this is not a problem with guard patterns because they already need an irrefutable alternative anyway. For example, we could rewrite the const pattern example with guard patterns as follows: ```rust From b55dfb7ca546ecc4b8702c365d223d5694eacfa8 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Fri, 23 Aug 2024 21:30:16 -0700 Subject: [PATCH 17/18] move mismatched bindings sections to open questions --- text/3637-guard-patterns.md | 111 +++++++++++++++++------------------- 1 file changed, 51 insertions(+), 60 deletions(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 0a29354b227..9cda63320d9 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -4,16 +4,19 @@ - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary + [summary]: #summary 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. # Motivation + [motivation]: #motivation 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. # Guide-level explanation + [guide-level-explanation]: #guide-level-explanation 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 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. # Reference-level explanation + [reference-level-explanation]: #reference-level-explanation ## Supersession of Match Arm Guards @@ -76,7 +80,7 @@ x @ A(..) if pred <=> (x @ A(..)) if pred A(..) | B(..) if pred <=> (A(..) | B(..)) if pred ``` -## Precedence Relative to `|` +## Precedence Relative to `|` Consider the following match expression: @@ -107,12 +111,12 @@ Therefore guard patterns appearing at the top level in those places must also be // Not allowed: let x if guard(x) = foo() {} else { loop {} } if let x if guard(x) = foo() {} -while let x if guard(x) = foo() {} +while let x if guard(x) = foo() {} // Allowed: let (x if guard(x)) = foo() {} else { loop {} } if let (x if guard(x)) = foo() {} -while let (x if guard(x)) = foo() {} +while let (x if guard(x)) = foo() {} ``` Therefore the syntax for patterns becomes @@ -121,15 +125,16 @@ Therefore the syntax for patterns becomes > _Pattern_ :\ >       _PatternNoTopGuard_\ >    | _GuardPattern_ -> +> > _PatternNoTopGuard_ :\ ->       `|`? _PatternNoTopAlt_ ( `|` _PatternNoTopAlt_ )\* +>       `|`? _PatternNoTopAlt_ ( `|` _PatternNoTopAlt_ )\* With `if let` and `while let` expressions now using `PatternNoTopGuard`. `let` statements and function parameters can continue to use `PatternNoTopAlt`. ## Bindings Available to Guards The only bindings available to guard conditions are + - bindings from the scope containing the pattern match, if any; and - bindings introduced by identifier patterns _within_ the guard pattern. @@ -157,20 +162,6 @@ let (Struct { x, y } if x == y) = Struct { x: 0, y: 0 } else { /* ... */ } 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. -## Bindings Must Still Match Across Disjunctions - -This RFC does _not_ propose to change what bindings are allowed in disjunctions, even when those bindings are used only within guard patterns. - -For example, the following code will error just like it would without any guard patterns: - -```rust -match Some(0) { - Some(x if x > 0) | None => {}, - //~^ ERROR variable `x` is not bound in all patterns - _ => {}, -} -``` - ## As Macro Arguments 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- 3. In the next edition, update `pat` fragments to accept `Pattern` once again. # Drawbacks + [drawbacks]: #drawbacks 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. # Rationale and alternatives + [rationale-and-alternatives]: #rationale-and-alternatives ## "Or-of-guards" Patterns @@ -203,7 +196,7 @@ Therefore, we could choose to restrict guard patterns so that they appear only i 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. Note that, currently, most patterns are actually parsed as an or-pattern with only one choice. -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. +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. There are, however, a couple reasons where it could be desirable to allow guards further inwards than strictly necessary. @@ -245,6 +238,7 @@ match order { ### Pattern Macros If guards can only appear immediately within or-patterns, then either + - 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 - 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. @@ -254,16 +248,15 @@ This can also be seen as a special case of the previous argument, as pattern mac 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. - 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: ```rust const FALSE: EvilBool = EvilBool(false); -const TRUE: EvilBool = EvilBool(true); +const TRUE: EvilBool = EvilBool(true); match EvilBool(false) { FALSE => {}, - TRUE => {}, + TRUE => {}, } ``` @@ -289,45 +282,20 @@ match EvilBool(false) { But this will always be a compilation error because the `match` statement is no longer assumed to be exhaustive. # Prior art + [prior-art]: #prior-art -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. +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 ]() languages. 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. # Unresolved questions -[unresolved-questions]: #unresolved-questions - -- How should we refer to this feature? - - "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. - - "`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. - - Some other possibilities: "condition/conditioned pattern," "refinement/refined pattern," "restriction/restricted pattern," or "predicate/predicated pattern." -- What anti-patterns should we lint against? - - Using guard patterns to test equality or range membership when a literal or range pattern could be used instead? - - Using guard patterns at the top-level of `if let` or `while let` instead of let chains? - - Guard patterns within guard patterns instead of using one guard with `&&` in the condition? - - `foo @ (x if guard(x))` rather than `(foo @ x) if guard(x)`? Or maybe this is valid in some cases for localizing match behavior? -- Is `pat_no_top_guard` a good name, or should we use something shorter like `pat_unguarded`? - -# Future possibilities -[future-possibilities]: #future-possibilities - -## Allowing `if let` - -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. - -One way to think about this is that patterns serve two functions: -1. Refinement: refutable patterns only match some subset of a type's values. -2. Destructuring: patterns use the structure common to values of that subset to extract data. - -Guard patterns as described here provide _arbitrary refinement_. That is, guard patterns can match based on whether any arbitrary expression evaluates to true. - -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. +[unresolved-questions]: #unresolved-questions ## Allowing Mismatching Bindings When Possible -Users will likely want to write something like +Ideally, users would be able to write something to the effect of ```rust match Some(0) { @@ -337,19 +305,42 @@ match Some(0) { ``` This is also very useful for macros, because it allows + 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 2. macro users to pass guard patterns to macros freely, even if the macro uses the pattern within a disjunction. As mentioned above, this case is not covered by this RFC, because `x` would need to be bound in both cases of the disjunction. -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. +### Possible Design -We could also make this capturing behavior explicit, with some kind of syntax extending guard patterns: +[@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: -```rust -// example syntax by analogy with closures -// probably not what we'd want to go with, since you can't specify which bindings are captured -Some(x move if x > 0) | None -``` +1. Unchanged: If a name is bound in any part of a pattern, it shadows existing definitions of the name. +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. +3. Removed: ~~Bindings introduced in one branch of a disjunction must be introduced in all branches.~~ +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.) + +## How to Refer to Guard Patterns + +Some possibilities: + +- "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. +- "`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. +- Some other possibilities: "condition/conditioned pattern," "refinement/refined pattern," "restriction/restricted pattern," or "predicate/predicated pattern." + +[future-possibilities]: #future-possibilities + +# Future Possibilities + +## Allowing `if let` + +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. + +One way to think about this is that patterns serve two functions: + +1. Refinement: refutable patterns only match some subset of a type's values. +2. Destructuring: patterns use the structure common to values of that subset to extract data. -This would also give the guard ownership of the bound value, which may be desirable in other cases. +Guard patterns as described here provide _arbitrary refinement_. That is, guard patterns can match based on whether any arbitrary expression evaluates to true. + +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. From ab79e9e183771a101a63ccb923125fb04553cbaf Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 4 Sep 2024 21:29:26 +0000 Subject: [PATCH 18/18] Prepare RFC 3637 to be merged --- text/3637-guard-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3637-guard-patterns.md b/text/3637-guard-patterns.md index 9cda63320d9..0681e3901e3 100644 --- a/text/3637-guard-patterns.md +++ b/text/3637-guard-patterns.md @@ -1,7 +1,7 @@ - Feature Name: `guard_patterns` - Start Date: 2024-05-13 - RFC PR: [rust-lang/rfcs#3637](https://github.com/rust-lang/rfcs/pull/3637) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +- Tracking Issue: [rust-lang/rust#129967](https://github.com/rust-lang/rust/issues/129967) # Summary