Rfcs: [Request] Overlapping match statements

Created on 18 May 2017  ·  48Comments  ·  Source: rust-lang/rfcs

The ability to specify in a match statement that given some results the execute in top down order of all the statements that match.

Example:
Current:

match cmp.compare(&array[left], &array[right]) {
    Less => {
        merged.push(array[left]);
        left += 1;
    },
    Equal => {
        merged.push(array[left]);
        merged.push(array[right]);
        left += 1;
        right += 1;
    },
    Greater => {
        merged.push(array[right]);
        right += 1;
    }
}

my proposal for the new syntax:

match all cmp.compare(&array[left], &array[right]) {
    Less, Equal => {
        merged.push(array[left]);
        left += 1;
    },
    Greater, Equal => {
        merged.push(array[right]);
        right += 1;
    }
}

Basically this would be an extension to the current patterns where a comma signifies an overlapping qualifier meaning that if the value matches the overlap then it executes the code and then continues on searching.

T-lang

Most helpful comment

How hard would it be to write a macro for this? It seems too niche to warrant a dedicated language feature to be honest.

All 48 comments

I think the syntax would need to be different... maybe match any instead of match? Otherwise, the behavior of things like this would change:

match some_value {
    Less => {...}
    Equal => {...}
    _ => {...} // does this execute always?
}

I think match needs checks for exhaustiveness and exclusivity, which this breaks. You could ask for | in if let instead though?

let x = cmp.compare(&array[left], &array[right]);
if let Less | Equal = x { .. }
if let Greater | Equal = x { .. }

I'd just use the ordering related impls for Ordering here though :

let x = cmp.compare(&array[left], &array[right]);
if x <= Equal { .. }
if x >= Equal { .. }

@mark-i-m Yes, I would say that the last line would always execute.

@burdges Yes, I do see that in this case a dual if check could work, but I would still say that the match would be easier to read even if it is sugar.

I agree with @burdges if this becomes a feature, it should be clearly distinct from normal match because of the very different behavior... I think exhaustiveness checks could still be done in the same way they are done for current match statements more or less.

I do wonder how commonly used such a feature would be though...

There is zero reason to use a match here except for wanting exhaustiveness checks. Yet, there are more situations in which you want exhaustiveness checks with if let constructions than situations in which you want this construct, like say

if let Less = x { .. }
foo();
if let Equal = x { .. }
bar();
if let Greater = x { .. }

I think the right solution is flexible manual exhaustiveness checks and | in if let, so roughly

let mut done = false;
let x = cmp.compare(&array[left], &array[right]);
if let Less | Equal = x { done=true; ..}
if let Greater | Equal = x { done=true; .. }
proof_assert!(done);

where proof_assert! asks the compiler to prove an assertion at compile time.

How hard would it be to write a macro for this? It seems too niche to warrant a dedicated language feature to be honest.

I agree that match would only be used with exhaustiveness checks but I think that a match any would be clearer then a new macro and if let

The advantage of the macro is that the feature can be experimented on without changing the language. I think having an existing impl in form of a macro will help the discussion significantly.

What sort of thing is this macro for? So that I can build it.

After looking into it I do not believe that a match all can be made as a macro

I have updated the RFC to change it to say match all

@Nokel81 Would something like this suit your purposes?

Yes it would if it did exhaustiveness testing

I think you could make a procedural macro for that... It would generate the code generated by the macro I posted above. Then, it could also generate a normal match state with the patterns. This will use the normal compiler exhaustiveness checking. The reason I think it should be a procedural macro is that I think that's the only way to de-duplicate the match arms...

Sorry but I don't know enough about macros to do that

In all honesty, neither do i, but I'm sure we could figure it out.

On Jun 23, 2017 11:07 PM, "Sebastian Malton" notifications@github.com
wrote:

Sorry but I don't know enough about macros to do that


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rfcs/issues/2003#issuecomment-310809787, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AIazwOSiTU33f3IXyEoMDuyK6Gf833fqks5sHH1ngaJpZM4NfmU5
.

It seems fairly straightforward to add a dummy match just for the purpose of exhaustiveness checking: https://is.gd/zGrXsy

Is there any reason that wouldn't be sufficient?

I also added a let binding for the discriminant to avoid repeating any side effects. Of course, this means it will only work with Copy types. I'm not sure what to do about that. (I think, if we had #2005, we could add an & there and it would just work out of the box?)

Also, try making that example actually exhaustive :)

But that is what i had in mind... It could be made less quirky as a
procedural macro, i think.

On Jun 24, 2017 12:08 AM, "Gábor Lehel" notifications@github.com wrote:

It seems fairly straightforward to add a dummy match just for the purpose
of exhaustiveness checking: https://is.gd/zGrXsy

Is there any reason that wouldn't be sufficient?

I also added a let binding for the discriminant to avoid repeating any
side effects. Of course, this means it will only work with Copy types.
I'm not sure what to do about that. (I think, if we had #2005
https://github.com/rust-lang/rfcs/pull/2005, we could add an & there
and it would just work out of the box?)


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rfcs/issues/2003#issuecomment-310812402,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AIazwHAtsJiFqZQDtii6jmRPpLosdCPQks5sHIukgaJpZM4NfmU5
.

@glaebhoerl The link that you provided does not compile.

(@Nokel81 Yes, that's the point - the match is not exhaustive.)

Good point, oops. So this works amazingly, I just need to find out how the else case might work

You might also want to add the attribute on the match:

#[allow(unreachable_patterns)]

Since the match will have some duplicate patterns... That's what I was trying to say above...

EDIT: it should be allow not warn

or rather, I was doing something more complicate with the procedural macro, but this works better...

I do have a question about the macro though. Is there a specific reason why it has to be None/Some?

No, just an example of patterns... It would work for any other matchable
thing.

On Jun 27, 2017 11:33 AM, "Sebastian Malton" notifications@github.com
wrote:

I do have a question about the macro though. Is there a specific reason
why it has to be None/Some?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rfcs/issues/2003#issuecomment-311396202,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AIazwLzBJIGE3W_wB5Ew6TqWtsw9wZTYks5sISDBgaJpZM4NfmU5
.

How would we write the macro so that the _ pattern only matches if none of the other patterns match? Is it possible to add a counter?

That wouldn't really make sense IMHO. since _ matches anything, it should
always execute... But I'm sure you did add a such a counter to the macro.

On Jul 6, 2017 3:49 PM, "Sebastian Malton" notifications@github.com wrote:

How would we write the macro so that the _ pattern only matches if none of
the other patterns match? Is it possible to add a counter?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rfcs/issues/2003#issuecomment-313500409, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AIazwJqd5MyBF72Y2aYn1f7ESPsTxciLks5sLTo3gaJpZM4NfmU5
.

What I really meant was for the existence of an else clause which would act kind of like the _ case for normal matches but only match if there were no other matches

Perhaps something like

match_all! {...} else {...}

would be more intuitive? But i really don't know how to build that...

On Jul 7, 2017 11:00 PM, "Sebastian Malton" notifications@github.com
wrote:

What I really meant was for the existence of an else clause which would
act kind of like the _ case for normal matches but only match if there
were no other matches


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rfcs/issues/2003#issuecomment-313829308,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AIazwGNJkvGV4fxKqqAh0drPEJoH_BWkks5sLvDWgaJpZM4NfmU5
.

I agree that that is more intuitive and I will try and make a macro when I have time (in about a week or so)

Actually, given the format of a match statement the , separateness is not required since using | already basically does that. So what is really required is

match_all val {
    val => expr,
    ....
} else {
    expr
}

Since the _ still matches everything, I would suggest that having both _ and else { } would be a compiler error of type Unreachable code on the else statement since it can never run.

Just to throw this into the discussion, this seems to be a functionally complete implementation of match_all, even if it is not an aesthetically complete implementation. The commented out line is an optional special-case that handles if no match occurred. If you would prefer that every match_all handles the condition of no match, this version provides that. Glancing through the discussion, it doesn't look like anyone has come up with these modifications to @glaebhoerl's code.

Do you know why using | results in a compilation error? If the syntax was instead using | instead of , then the bug of a result being done multiple times could be fixed. I though that pat | pat was also a pat

If the syntax was instead using | instead of , then the bug of a result being done multiple times could be fixed.

Can you provide an example of both the bug and what the match_all would look like with that change? I'm not sure I understand what bug would be avoided, or what the final match_all would look like. As for why it's a compilation error, macro_rules! has some annoying limitations on what is acceptable, but this isn't one of those times. Should $x:pat | $y:pat being fed Some(3) | None have $x = Some(3) | None such that $y doesn't have a value? If you have more than one | bar, how do you divide the pattern up? If pat | pat => pat is true, there's no clear solution for when $y would _ever_ receive a value. It would be either impossible or random for $y to receive a value.

Here is an example, what I mean is that if a pattern appears twice in the same pat/expr pair then the expr will be executed multiple times.

And when I was saying that | doesn't work as a pattern, I had removed the ($p:pat),+ and made it just $p:pat which I thought would except |

I understand now. It's worth nothing that the if let syntax only allows for one pattern, not multiple, so this is a syntax error:

let value = Some(4);
if let Some(4) | None = value {
    println!("yay");
}

This version uses or-bars instead of commas for aesthetics. (does not fix the repeated condition issue)

And now, this other version uses or-bars and prevents duplicate execution of a match branch.

I was just about to post that 😀 Though my version just uses a boolean check

I would guess that using break makes it clearer to the optimizer that it can jump straight to the end, rather than checking each of the other conditions, as soon as one test case passes. i have been wrong about optimizers many times in the past, but that's my guess.

With and without optimizations using my version of the code. I just love how Rust and LLVM are able to optimize the high-level abstraction to just three instructions necessary to call both test3 and test4, in this case where it unrealistically has full knowledge of everything.

That. Is. Insane. But no matter, I think that this macro enables everything in the rfc so now to put it onto cargo. Do you care who does?

I don't care who publishes it, feel free to! And I don't really care if I get listed as a contributor or not, but it's always fun to pump up my count of crates I've helped author! XD

If you want to list me as an author, I'm the first author here, and you can get the author string there, but I really don't care either way! I only did a little bit.

Here is the crate. I have added the ability to use the match_all with IfNoMatch branch for variable assignment

Where is the repository? It would be nice to have some example code in the docs, so I was thinking about sending a pull request.

https://github.com/nokel81/match_all. The readme does but I cannot find a place to put the readme as the description

PR sent

Closing as the associated and linked RFC was closed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

onelson picture onelson  ·  3Comments

3442853561 picture 3442853561  ·  3Comments

mqudsi picture mqudsi  ·  3Comments

rudolfschmidt picture rudolfschmidt  ·  3Comments

3442853561 picture 3442853561  ·  3Comments