Can't match against constant expressions?


#1

match allows you to match against a literal constant:

match x {
    0 => println!("zero!"),
    _ => ...
}

but you can’t even parenthesize it; this is a syntax error:

match x {
    (0) => ...

but you can hide it in a singleton tuple:

match (x,) {
    (0,) => ...

but in either case you can’t use any kind of constant expression:

match (x,) {
    (0+0,) => ...

What’s the actual syntax here? Disallowing constant expressions seems pretty limiting, esp for any kind of generated code which wants to express things as constant expressions. Is there a plan to allow more general constants in match arm patterns?

(Also, rustc stops after reporting the first syntax error, which is inconvenient - it would be much better to report as many errors as possible so they can be fixed in one pass.)

https://play.rust-lang.org/?gist=63a33e2bf876c984c401&version=stable


#2

This is the case for syntax errors, in other cases you will get all errors emitted by the last compiler pass. But for syntax errors, writing the parser in a way that it can potentially recover from them is a hassle and just not worth it.


#3

It’s definitely a hassle, but it’s also definitely worth it. It’s the foremost error users experience, especially when still learning the language. Good error recovery is one of the key features of a solid production parser vs a prototype.

(It also means that the playground link I posted isn’t as useful as I’d like because I wanted it to demonstrate all the failures at once.)


#4

I don’t agree - it’s much more important that individual error messages are concise and understandable. That is an area where Rust is sometimes lacking, although this is a focus of much improvement work.

Error recovery doesn’t give you much for a language parser - it’s not like you’d further process the result anyway. Of course this is completely different with other parsers, e.g. the one parsing HTML in this browser :slight_smile:


#5

I agree, but I think that’s primarily an issue in type and other higher-level errors. Syntax errors tend to be fairly concrete and often localized (except for things like missing closes, etc).

Sure - the primary value is in presenting as many errors as possible in one pass, rather than requiring iterated compilation. It’s an area that mature compilers do spend a lot of effort in, because its one that gives material usability improvements. I’m not saying it’s Rust’s most important issue, but I disagree that it’s “not worth the effort”.

I think for things like run-away close brace/bracket, rustc does a pretty good job of identifying where it’s happened, and I think that’s fine for that kind of structural syntax error. But for local errors, it can do a better job of continuing from the next relevant piece of input. rustc’s parser is hand-coded, so in principle it can have arbitrary error recovery strategies, but that does also imply a lot of work. A good parser generator can do a good job with less manual effort (with the added benefit of working from a somewhat formal specification of the grammar).


#6

Anyway, in regard to the main question: the match expression isn’t just a row of if-equal clauses, it matches against a pattern. Patterns basically destructure types, so you can match against type constructors, of which tuples are an example.

Allowing constant expressions would probably be possible, but I don’t want to speculate on the full impact on the grammar right now. It also blurs the line between patterns and expressions a bit more, which I’m not sure the language designers would like. For example,

let a = 1;
match x {
    1 + 1 => { ... }
    a + 1 => { ... }
}

The first arm would be allowed. The second arm would be disallowed.


#7

Haskell (used to?) allow patterns of the second form.


#8

Haskell used to have so-called n + k patterns, but they were a weird special case which many people thought was a bad idea, and they were removed in Haskell 2010.
I should also note that while n+k patterns would have allowed the a + 1 arm, they wouldn’t have allowed the 1 + 1 arm.


#9

You can always move the constant expression to a constant:

const ZZ: u32 = 0 + 0;
match x {
    ZZ => true,
    _ => false,
}

The reason for this is that patterns in Rust are the exact opposite of the construction of the object you destructure. The + operation could be seen as a “constructor”, and it could probably be done for constants, but it would be very confusing as to which feature exists and which does not:

Is the following a destructuring of an enum variant (or tuple struct) or is it a const-fn call? The parser can obviously not know which it is, since that would require type information.

match x {
    f(5) => true,
    _ => false,
}