Guard Patterns
the keyword if is currently in the follow-set of a $:pat:
macro_rules! check_pat {
($p:pat) => { 1 };
($p:pat if $e:expr) => { 2 };
}
fn main() {
assert_eq!(check_pat!(Some(3) | None), 1);
assert_eq!(check_pat!(Some(e) if e == 3), 2);
// assert_eq!(check_pat!((Some(e) if e == 3)), 1); // <- workaround for existing editions
}
this means at minimum this RFC will require a new Edition, or require that $:pat can only match PatternNoTopGuard
the keyword
ifis currently in the follow-set of a$:pat:macro_rules! check_pat { ($p:pat) => { 1 }; ($p:pat if $e:expr) => { 2 }; } fn main() { assert_eq!(check_pat!(Some(3) | None), 1); assert_eq!(check_pat!(Some(e) if e == 3), 2); // assert_eq!(check_pat!((Some(e) if e == 3)), 1); // <- workaround for existing editions }this means at minimum this RFC will require a new Edition, or require that
$:patcan only match PatternNoTopGuard
True, forgot to check that. I think I'd prefer making pat fragments match only PatternNoTopGuard to gating it on a new edition, since you can always just wrap the guard in parentheses to invoke a pattern with it.
True, forgot to check that. I think I'd prefer making
patfragments match onlyPatternNoTopGuardto gating it on a new edition, since you can always just wrap the guard in parentheses to invoke a pattern with it.
I think a better plan is to make :pat match PatternNoTopGuard on edition 2021, and then the next edition (2024 if we're not too late already, otherwise whatever edition comes after that) would have :pat match Pattern. There would also be a :pat_no_top_guard (or some shorter name) that works on all editions.
This means this feature can be stabilized without waiting for a new edition.
This is just like what happened with :pat and :pat_param
https://doc.rust-lang.org/edition-guide/rust-2021/or-patterns-macro-rules.html
This may be a bit vague, but running an arbitrary logic for conditions in patterns sort of goes against the idea of a "pattern".
Pattern is a "leaf" element of a condition and if you need to follow up on it using executable logic, you either
- open a new block
{}, like in if-let - or use some sugar to resyntax that block into a chain, ideally like
expr is pat(binding) && condition(binding), or with if-let chains as a handicapped variant of that.
(This mostly applies to nonexhaustive matching, but matches involving if guards are by definition non-exhaustive and have to have fallback "else" clauses at some level.)
The subscription_plan example in particular looks like an if-else chain forced into a match, and would look better either without a match, or without if guards.
Basically, my general conclusion is that not everything should be a match, regular ifs and elses are good too.
I have encountered the specific issue of two match arms being the same but not mergeable, but not too often.
Maybe a compromise solution extending match arms but not extending patterns would be suitable (since match arms already allow executable conditions anyway).
There are other situations where it's useful, not involving if guards, for example
match foo {
A(_want_to_name_this_binding_explicitly_for_exhaustiveness_even_if_it_is_not_used) => {
// same logic
}
B /* no equivalent binding here */ => {
// same logic
}
}
There are other situations where it's useful, not involving if guards, for example
match foo { A(_want_to_name_this_binding_explicitly_for_exhaustiveness_even_if_it_is_not_used) => { // same logic } B /* no equivalent binding here */ => { // same logic } }
if you need to merge the two branches you could just use a comment instead of a real name?
match foo {
A(_ /* name this binding explicitly for exhaustiveness */) | B => {
// logic
}
}
the keyword
ifis currently in the follow-set of a$:pat:macro_rules! check_pat { ($p:pat) => { 1 }; ($p:pat if $e:expr) => { 2 }; } fn main() { assert_eq!(check_pat!(Some(3) | None), 1); assert_eq!(check_pat!(Some(e) if e == 3), 2); // assert_eq!(check_pat!((Some(e) if e == 3)), 1); // <- workaround for existing editions }this means at minimum this RFC will require a new Edition, or require that
$:patcan only match PatternNoTopGuard
@kennytm @programmerjake
I've added a section explaining how we can handle this the same way it was handled for or-patterns.
This may be a bit vague, but running an arbitrary logic for conditions in patterns sort of goes against the idea of a "pattern".
I think that allowing guards in nested patterns is a natural step after allowing | in nested patterns, which is done since 1.53.
Guards are already part of the match construct, and allowing it in inner patterns doesn't actually bring a new functionality, it's just syntax sugar. The compiler is presumably allowed to hoist the guards outside the patterns (if they guarantee the guard only runs once), like you would do manually today.
(Outside match, adding an inner guard means just wrapping the whole thing inside an enclosing if; again, just syntax sugar)
The only issue with guards for me is that it trips the exhaustiveness checking (the compiler doesn't know if two or more guards cover all possibilities, so you need to delete the last guard to make it happy), but that's already an issue for the current guard feature in match, and allowing inner guards don't make it any worse.
(There is an analogous issue with bare if: the compiler can't detect if two or more successive if /else if are exhaustive and you need to replace the last one with else, and this is relevant when analyzing control flow)
But anyway I think that this language feature is just syntax sugar and should be approached as such.
This feels like a direct counter to the proposal in #3573: instead of allowing patterns as conditions, allow conditions as patterns. And I'll be frank, for the reasons mentioned, I don't like this.
Adding a condition to a pattern essentially means that the pattern isn't covered at all, and so, it's just adding the pattern and condition together as a single condition, with the minor difference that if the pattern was covered completely in a previous branch, it'd be noticed as dead code.
So... I would much rather have this just as the ability to combine patterns in an if statement beforehand, rather than a way of making match arms more confusing.
Would this allow disjoint sets of bindings? e.g.
(A(x, y) if x == y) | B(x) => ...
To me it seems intuitive that this would be allowed (as it would now have a reason to exist, whereas before there would be no reason to bind a variable in only one branch of an or-pattern). Trying to access x would give a this variable was not bound in all branches error, or maybe allow accessing an x from an outer scope? Not sure about that.
(A(x, y) if x == y) | B(x) => ...
x is bound in both branches, maybe you meant y since that's not mentioned in the B branch?
This seems reasonable. I've wanted this a few times myself, and it's always inconvenient to work around.
It looks like the issue of edition handling has been addressed, and the issue of if let guards is covered in "future work" (which seems sufficient since if let guards aren't stable yet).
Let's see if we have a consensus on this:
@rfcbot merge
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members:
- [x] @joshtriplett
- [x] @nikomatsakis
- [ ] @pnkfelix
- [x] @scottmcm
- [ ] @tmandry
Concerns:
- ~~binding-rules-clarification~~ resolved by https://github.com/rust-lang/rfcs/pull/3637#issuecomment--2002179957
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. See this document for info about what commands tagged team members can give me.
@rfcbot reviewed
This seems like a logical extension, I can't really see any reason not to do this.
Kind of disappointed that very few folks seem to have acknowledged my comment as this goes forward: https://github.com/rust-lang/rfcs/pull/3637#issuecomment-2118272642
Combining arbitrary conditions and patterns results in a new condition, not a new pattern, IMHO. So, I think that ultimately, the ability to use a syntax like is to access destructured components instead of complicating patterns makes a lot more sense.
Because like I said, ultimately, conditional patterns do not act as patterns; the compiler cannot reason that conditions are disjoint, and so, a general case must always be provided. The match guard syntax really just is a shorthand that helps reduce code duplication on fallback conditions, and that's about it.
I've yet to seen code that's simpler with this change, compared to is syntax which both removes the need for matches! and adds more potential uses. I would prefer that much of this example code be reworded into if branches with matches! and/or is instead of guard patterns.
I don't see why this RFC and #3573 have to be put into an exclusive relationship. They can be both accepted or both rejected/postponed.
Can I put if let in a guard pattern? What about let-chains?
@clarfonthey the interaction with exhaustiveness is interesting, can you elaborate-- are there surprising cases where the exhaustiveness check results are not what users would probably expect?
@clarfonthey the interaction with exhaustiveness is interesting, can you elaborate-- are there surprising cases where the exhaustiveness check results are not what users would probably expect?
So I will apologise since I haven't remembered to respond to this while I'm at my computer and can type out a more nuanced response to this, so, here. Also noting that I'm not specifically explaining at you, Niko, just jumping off your comment for my explanation, so, I'm going to give a broader explanation for a wider audience. I also apologise because as I wrote this, it became a bit of a mess, but I hope that my points still get across okay.
So, as a syntactic construct, patterns are useful as a form of destructuring: you can pull the x out of Some(x) with a pattern, and that's something pretty unique to them. This is something that you can do with match arms, if let, and ordinary let if the pattern is exhaustive.
However, another feature of patterns which is useful is exhaustion, where you can match on every possible case of a given type. Pattern exhaustion includes enum variants, but it also includes things like integer ranges: 0 and 1..=255 fully exhaust the u8 type, for example.
I'm going to argue that the reason why guards are desired is destructuring, but the reason why they shouldn't be included in patterns is because they disrupt the reasoning on exhaustion.
Let's look at a simple match statement:
match obj {
Obj::B(b) => do_bool(b),
Obj::I(i) => do_int(i),
Obj::F(f) => do_float(f),
Obj::S(s) => do_string(s),
}
The moment we add guards to our match statement, things get a bit muddier:
match obj {
Obj::B(b) => do_bool(b),
Obj::I(i) if i > 0 => do_pos_int(i),
Obj::I(0) => do_zero(),
Obj::I(i) if i < 0 => do_neg_int(i),
Obj::F(f) => do_float(f),
Obj::S(s) => do_string(s),
}
As you can see, Rust isn't necessarily able to reason about the fact that i > 0, i = 0, and i < 0 fully covers all possible values of i. As a programmer, we know this, but Rust can't reason with arbitrary conditions, even simple ones. So, we need to add an extra unreachable arm if we want to continue with this method:
match obj {
Obj::B(b) => do_bool(b),
Obj::I(i) if i > 0 => do_pos_int(i),
Obj::I(0) => do_zero(),
Obj::I(i) if i < 0 => do_neg_int(i),
Obj::I(_) => unreachable!(),
Obj::F(f) => do_float(f),
Obj::S(s) => do_string(s),
}
Of course, with patterns, there is a way around this:
match obj {
Obj::B(b) => do_bool(b),
Obj::I(i @ 1..=i64::MAX) => do_pos_int(i),
Obj::I(0) => do_zero(),
Obj::I(i @ i64::MIN..=-1) => do_neg_int(i),
Obj::I(_) => unreachable!(),
Obj::F(f) => do_float(f),
Obj::S(s) => do_string(s),
}
And note that when we use patterns, we can easily rearrange them, but with guards, we can't. Contrived example:
match obj {
Obj::B(b) => do_bool(b),
Obj::I(0) => do_zero(),
Obj::I(i) if i >= 0 => do_pos_int(i),
Obj::I(i @ i64::MIN..=-1) => do_neg_int(i),
Obj::I(_) => unreachable!(),
Obj::F(f) => do_float(f),
Obj::S(s) => do_string(s),
}
will work properly, but the below will not:
match obj {
Obj::B(b) => do_bool(b),
Obj::I(i) if i >= 0 => do_pos_int(i),
Obj::I(0) => do_zero(),
Obj::I(i @ i64::MIN..=-1) => do_neg_int(i),
Obj::I(_) => unreachable!(),
Obj::F(f) => do_float(f),
Obj::S(s) => do_string(s),
}
Since, unlike patterns whose overlaps can be reasoned about, conditions cannot. And again, while we could always add in select options to the compiler to be able to reason about simple conditions, the point of patterns is to explicitly not have to do that.
And note: patterns can overlap, but the compiler can notice, remove the overlaps, and then reorder the patterns. For example, if we had Obj::I(0) and then Obj::I(i64::MIN..=0), the compiler could realise that the second pattern becomes Obj::I(i64::MIN..=-1) and then reorder the two patterns. It can always do this because patterns are simply statements about the set of possible values for a type, and simple set theory operations can be applied.
Back to the beginning, the specific reason why we even want to have guards in patterns to begin with is destructuring: you can't easily do matches!(obj, Obj::I(i)) && i >= 0 because the i in the matches! does not leave the scope of the matches. However, with is patterns, obj is Obj::I(i) && i >= 0 works as intended.
Essentially, my proposal here is that conditions be confined to conditional branches (like if let or maybe match guards), and that the pattern syntax explicitly exclude if guards. So, this example from the RFC:
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.
}
}
Which I'm going to update to make more usage of destructuring, since this could be solved with a simple == instead:
match user.subscription_plan() {
(Plan::Regular(credits) if credits >= 100) | (Plan::Premium(credits) if credits >= 80) => {
// Complete the transaction.
}
_ => {
// The user doesn't have enough credit, return an error message.
}
}
should be written as:
let plan = user.subscription_plan();
if (plan is Plan::Regular(credits) && credits >= 100) || (plan is Plan::Premium(credits) && credits >= 80) {
// Complete the transaction.
} else {
// The user doesn't have enough credit, return an error message.
}
And while I haven't read the full thread in detail, I don't really think that there are many cases where adding guards which can arbitrarily be nested inside patterns would be easier than just using is instead, but I could very well be wrong and I would love to see examples! But all of the simple examples I've seen really just, don't support the idea of being worth the complexity.
And, it would also provide a lot of confusing ways to write the same thing in different ways with is patterns: should you write plan is Plan::Regular(credits) if credits >= 100) or plan is Plan::Regular(credits) && credits >= 100? How should parentheses be applied here? What does that mean? It just opens too many questions for a solution that doesn't seem to solve much.
should you write
plan is Plan::Regular(credits) if credits >= 100)orplan is Plan::Regular(credits) && credits >= 100? How should parentheses be applied here? What does that mean?
Easy, according to #3573 the grammar is IsExpression ::= Expression "is" PatternNoTopAlt, i.e. expr is P | Q already means (expr is P) | Q. The if guard has even lower precedence than | so expr is P if cond should be parsed as (expr is P) if cond (which is syntax error).
@clarfonthey
It seems to me as though you're really arguing against guards in general, rather than specifically guard patterns. I don't disagree that it's desirable for the compiler to be able to detect pattern exhaustiveness, but regardless of this guards are clearly a useful feature — and not one we can remove in the first place, since it would be extremely breaking.
And while I haven't read the full thread in detail, I don't really think that there are many cases where adding guards which can arbitrarily be nested inside patterns would be easier than just using is instead, but I could very well be wrong and I would love to see examples! But all of the simple examples I've seen really just, don't support the idea of being worth the complexity.
You are correct that the examples I gave in the RFC could be written as equivalent if statements, as they only contain two distinct branches. My intention was to make the examples as simple and easy to read as possible, but most match statements can't be so easily rewritten as if statements. For example, consider the following slight modification to that same example:
match user.subscription_plan() {
Plan::UltraPremium => {
// This item is free for ultra premium users. Complete the transaction with no billing.
}
(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.
}
}
If you wanted to do this using if and is, you would have to write
let plan = user.subscription_plan();
if plan == Plan::UltraPremium {
// This item is free for ultra premium users. Complete the transaction with no billing.
} else if (plan is Plan::Regular && user.credit() >= 100) || (plan is Plan::Premium && user.credit >= 80) {
// Complete the transaction.
} else {
// The user doesn't have enough credit, return an error message.
}
Now imagine we were to add even more plans with special handling; this gets verbose quite fast compared to a match expression. But most importantly, this code does not at all imply that we're exhausting the possible values of user.subscription_plan(). A match expression forces you to be exhaustive, whereas an if statement does not, even with is expressions.
Even beyond the cases where they're necessary to reduce code duplication, they can be useful for organizing match behavior to be more easily readable. Specifically, you can move guard conditions close to the values they consider so they're more easily read. I gave an example of this in the RFC.
Like Niko mentioned, I'd be very interested to see examples of guard patterns specifically (as opposed to guards as they exist today) behaving in counterintuitive ways.
@rustbot labels -I-lang-nominated
We discussed this in the lang call today. People felt rather positively about this, but wanted some time to think about any reasons that we wouldn't want to do this.
One quirky aspect of this feature is that it would make some details of our match MIR generation algorithm visible. E.g., with today's implementation, we'd probably get:
match ((0, 0), false) {
// `f` is not called
((x, 0) if f(x) > 0 | (0, x) if f(x) < 0, true) => {}
_ => {}
}
match ((0, 0), false) {
// `f` is called
((x, 0) if f(x) > 0 | (0, x) if f(x) < 0, true | true) => {}
_ => {}
}
The match lowering algorithm is full of quirks like that, and we'd like to keep the freedom to change it to generate better code. In other words, I expect we'd want to explicitly refuse to specify exactly which guard is executed when. Not a blocker but feels important to note.
Unrelatedly, idk if anyone mentioned it yet but combined with if let guards this makes it possible to emulate deref patterns:
macro_rules! deref {
($p:pat) => { x if let $p = x.deref() }
}
match Some(vec![0]) {
Some(deref!([0, 1, x])) => ...,
...
}
which is extremely cool.
I expect we'd want to explicitly refuse to specify exactly which guard is executed when.
I disagree. If I write:
match foo {
((x, y) if cheap_and_likely(x)) | ((x, y) if slooow(x)) => 42,
_ => 15,
}
I want cheap_and_likely to be called first. I think we should guarantee as-if equivalence to the following algorithm:
- For each or-pattern alternative, check whether the scrutinee matches disregarding
ifguards. Discard alternatives that definitely fail to match. - Go through the remaining alternatives one by one, calling guards in source order, and stopping as soon as a match is confirmed.
We can absolutely guarantee simple cases like that. We should also guarantee the relative order of running guards (they must be run left-to-right and top-to-bottom).
The leeway I'd want as an implementor is something like "a guard that is part of a pattern that doesn't match the scrutinee may be skipped or may be run (but if it is run it is in expected order wrt other guards).
According to the reference, apparently today's match guards will be run once per or pattern alternative. IOW, a guard can be executed several times in the course of a match. Which, uh, why?!
(Edit: because they can refer to bindings defined by the alternatives)
This would be getting off-topic, so I answered this on Zulip.
To your point @clarfonthey, my deref pattern emulation does lack exhaustiveness, and that is unfortunate. I think this is well worth the cost however. Quite generally, if-let guard patterns allow for patterns that don't strictly follow the shape of the data:
pub enum MySmallVec<T> {
Empty,
Single(T),
Many(Vec<T>),
}
fn foo(x: Option<MySmallVec<u64>>) {
match x {
Some(vec if let [0, ..] = vec.as_slice()) => ...
...
}
}
pub struct MyRange {
start: usize,
len: usize,
}
fn bar(x: Option<MyRange>) {
match x {
Some(range if let (_, 100) = range.endpoints()) => ...
...
}
}
(I know these example can be written without guard patterns, but this is about whether this kind of thing is first-class or not. In particular, guard patterns let me hide that behind a macro).
Admittedly deref patterns by themselves go a long way towards that kind of thing. Still this feels like it solves an important flexibility issue in patterns, namely that to allow users to pattern-match on your data structure you have to expose its internals (and they have to be matcheable, and they can never change again).
Allow me to get excited at the possibilities:
fn foo(map: &mut HashMap<String, u64>) {
match map {
hashmap_mut!{ "x": x, "y": 42, .. } => *x += 1,
// expands to:
map if let Some(x) = map.get_mut("x")
&& let Some(42) = map.get_mut("y") => *x += 1,
_ => ...
}
}
fn bar(cell: &RefCell<u64>) {
match cell {
borrow_mut!(x @ 1..) => *x = 0,
// expands to:
c if let mut borrow = c.borrow_mut()
&& let x @ 1.. = &mut *borrow => *x = 0,
_ => ...
}
}
struct ParseTree {
text: &str,
nodes: Vec<ParseTree>,
}
fn baz(tree: &ParseTree) {
match tree {
node!(Mul(node!(0), x)) => ...,
// expands to:
ParseTree { text: "Mul", nodes: deref!([
ParseTree { text: "0", nodes: deref!([]) },
x,
]) } => ...,
...
}
}
@clarfonthey
It seems to me as though you're really arguing against guards in general, rather than specifically guard patterns. I don't disagree that it's desirable for the compiler to be able to detect pattern exhaustiveness, but regardless of this guards are clearly a useful feature — and not one we can remove in the first place, since it would be extremely breaking.
Yes, I agree that we can't remove match guards, and I'm not proposing we do that. However, you're right that I am kind of against guards for the reasons I mentioned. I do think that overall, it's better to avoid them whenever possible, and I think that this RFC is against the spirit of that.
Now imagine we were to add even more plans with special handling; this gets verbose quite fast compared to a match expression. But most importantly, this code does not at all imply that we're exhausting the possible values of
user.subscription_plan(). A match expression forces you to be exhaustive, whereas anifstatement does not, even withisexpressions.
Funnily enough, this is precisely the reason why I am arguing against guard patterns here. Effectively, a _ pattern is equivalent to an else, and while that still exists, you have not gained much over an if statement if you're using the form you're describing.
I do have trouble coming up with a good case where if guards are counter-intuitive, but again, I feel like I've laid some groundwork that might help find some cases like that. If an example does come up and I think of it, I will mention it here.
I think that a good term for what I've been describing is "zero-width" patterns, which is, essentially, patterns that must cover zero possible values due to their addition of a condition. A zero-width pattern can still overlap with things and thus be redundant, but it can never add additional coverage to a set of patterns.
As an example, consider Plan::Regular if condition. This pattern is zero-width in the sense that it cannot assume that the Plan::Regular pattern is fully covered, but it can overlap with an already-covered, general Plan::Regular case if it exists.
The main confusion concerning these "zero-width" patterns is that they slowly erode some of the things we're able to do with patterns. As mentioned before, strict ordering of conditions is required, and this removes the ability to reorder patterns to potentially speed up matching. It also makes exhaustiveness difficult to check: while your conditions may actually exhaust the full space of values, you're still required to add an unreachable!() branch that also cannot be reordered with respect to the other checks. And again, all of this is for the sake of something which is, for better or worse, equivalent to a regular series of if branches that were hand-written anyway.
Right now, matches are already pretty slow when they involve complex patterns, even though they can "in theory" be sped up more based upon all of the reordering I mentioned. A good example of this is rust-lang/rust#122685, which proposes actually linting on complex match expressions, since they can be quite slow both on the compiler and at runtime. Adding the ability for arbitrary logic to be nested arbitrarily deep in match expressions is only going to make this worse, except in a way that it cannot possibly have its performance improved.
To summarise:
- Adding conditions to patterns essentially solidifies the patterns as something that is slow and unoptimisable
- Adding conditions to patterns removes the ability to reason about the exhaustiveness of patterns, adding in extra
unreachable!()branches which cannot easily be removed by the compiler - The
issyntax allows many of the desired statements to be rewritten as a series ofifbranches where complex logic can be embedded, as an alternative to creating these more complicated patterns
Essentially, while this creates a case that is "easier" on the surface from the perspective of people writing and reading them, this "easiness" hides a lot of underlying complexity that actually makes the code being run more complicated. And I think that this complexity should actually be reflected in the code being written, rather than being hidden away in syntax.
To me, seeing the branches explicitly written out gives no ambiguity on what is being done: these conditions are being checked in order. However, seeing them embedded in patterns gives the false impression that they may be reordered or done all at once, which isn't the case. And, as I've said, it effectively adds two ways of writing the same thing when they have virtually no impact on the actual code being run, while giving the impression they do.
Right now, matches are already pretty slow when they involve complex patterns, even though they can "in theory" be sped up more based upon all of the reordering I mentioned.
I'd like a citation for that. Matches aren't particularly worse than writing similar conditions by hand. Our compilation algorithm isn't very clever yet but it's decent. And pattern guards don't make things harder for the compiler: it can deal with if expressions just the same whether they're inside or outside patterns.
A good example of this is rust-lang/rust#122685, which proposes actually linting on complex match expressions, since they can be quite slow both on the compiler and at runtime.
I believe you are misunderstanding the purpose of this proposed lint. First, this has nothing to do with runtime performance. This is purely a stopgap measure for the unavoidable fact that checking exhaustiveness of a pattern can take exponential time in the size of the pattern. Pattern guards don't make this worse or better.
Adding conditions to patterns essentially solidifies the patterns as something that is slow and unoptimisable
Pattern guards do not influence the optimizability of patterns that don't use guards. And the non-guard parts can be optimized as usual even when there are guards. E.g.:
match (x, y, z) {
(true, true, <big pattern with guards>) => ...,
(false, true, <second big pattern with guards>) => ...,
(true, true, <last big pattern with guards>) => ...,
...
}
// will compile to something like:
if x {
if y {
if <big pattern with guards> { ... }
else if <last big pattern with guards> { ... }
}
} else if y && <second big pattern with guards> {
...
}
...
The compiler will not do clever things with the contents of the guards of course, but I doubt anyone expected it to do that (except maybe when it's hidden behind macros?). I guess here's something like that would be misleading:
match map {
hashmap!{ "x": x, "y": 42, .. } => ...,
hashmap!{ "x": x, "y": 43, .. } => ...,
_ => ...,
}
A user might expect that to only check the "x" and "y" keys once.