Can't match against constant expressions?

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.)

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.

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.)

1 Like

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:

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).

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.

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

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.

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,
}