Enabling associated `const` in patterns when there is a catchall case

To my surprise the following fails to compile:

trait Name: Eq + PartialEq {
    const FOO: Self;
    const BAR: Self;
}

fn is_foo_or_bar<N: Name>(name: N) -> bool {
    matches!(name, N::FOO | N::BAR)
}

with the error message:

error[E0158]: associated consts cannot be referenced in patterns
 --> src/lib.rs:7:20
  |
7 |     matches!(name, N::FOO | N::BAR)
  |                    ^^^^^^

error[E0158]: associated consts cannot be referenced in patterns
 --> src/lib.rs:7:29
  |
7 |     matches!(name, N::FOO | N::BAR)
  |                             ^^^^^^

According to the Error code E0158 documentation:

Associated consts cannot be referenced in patterns because it is impossible for the compiler to prove exhaustiveness (that some pattern will always match).

However the matches! macro is of course defined as:

macro_rules! matches {
    ($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?) => {
        match $expression {
            $pattern $(if $guard)? => true,
            _ => false
        }
    };
}

And I would think that the compiler should be able to detect the _ => ... catch-all arm and know that the match is exhaustive and thus allow the use of associated consts even if the compiler doesn't know the values of the associated constants. Am I missing something? (I'd be interested in contributing such a catch-all detection if it's not too complicated.)

3 Likes

Associated consts, to because they can't be converted into a pattern, would have to use PartialEq semantics or something instead of pattern matching semantics which could be confusing.

I think it's better to just have people use if guards in these cases:

// doesn't make sense for matches
matches!(name, x if x == N::FOO || x == N::BAR)
// just use the conditional directly
name == N::FOO || name == N::BAR

// but works fine in match
match name {
    x if x == N::FOO || N::BAR => { ... }
    SOMETHING_ELSE => { ... }
    _ => { ... }
}

Like regular constants associated constants already can be used in patterns with PartialEq semantics. The following compiles and works as you'd expect:

const FOO: &str = "foo";

struct Foo;

impl Foo {
    const BAR: &str = "bar";
}

trait Baz {
    const BAZ: &'static str;
}

impl Baz for Foo {
    const BAZ: &'static str = "baz";
}

fn is_foo_bar_or_baz(name: &str) -> bool {
    matches!(name, FOO | Foo::BAR | Foo::BAZ)
}

It's just when using associated constants from a generic type parameter that the described limitation (and E0158 occurs). As long as you follow the naming conventions of using UPPERCASE for constants, I don't think it's confusing at all. Yes indeed if guards work as a workaround however having a large match block with many if guards is quite ugly if it could be more elegantly expressed with just patterns. (And using the conditional directly is quite verbose when there are many values that you want to compare against.) The current limitation is especially annoying when attempting to use them in nested patterns.

1 Like

Those actually don't use PartialEq semantics, the compiler deconstructs the constant value into a pattern for use in those cases.

You can prove this by creating a type with a custom PartialEq impl - it won't be called during matching.

Creating a pattern from an associated constant from a generic parameter isn't supported because it's not available early enough during compilation.

I've expressed this elsewhere, but I think that PartialEq should not be invoked implicitly in a match. If guards make that explicit. Implicitly invoking PartialEq can have performance implications as well.

Edit: missed that not (thanks @push-f)

3 Likes

If StructuralEq (and the structural_match feature) were stable, and used as a trait bound for N (or supertrait of Name) then presumably there should be no problems compiling the match (eg. no post-mmorph errors), except possible implementation difficulty? The code doesn't compile on nightly even using those, but is that just a case of "not implemented yet"?)

3 Likes

Ah ... indeed ... thanks for pointing that out! I was led astray by the following code:

struct Foo;

trait Bar {
    const BAR: Self;
}

impl Bar for Foo {
    const BAR: Self = Foo;
}

fn is_foo_bar_or_baz(name: Foo) -> bool {
    matches!(name, Foo::BAR)
}

producing the compiler error:

error: to use a constant of type `Foo` in a pattern, `Foo` must be annotated with `#[derive(PartialEq, Eq)]`
  --> src/lib.rs:12:20
   |
12 |     matches!(name, Foo::BAR)
   |                    ^^^^^^^^
   |
   = note: the traits must be derived, manual `impl`s are not sufficient
   = note: see https://doc.rust-lang.org/stable/std/marker/trait.StructuralEq.html for details

(I didn't read the note and didn't know about StructuralEq)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.