How to handle pattern matching on constants

(To be honest, I’m surprised that you’re surprised! My understanding of / intuition regarding how const should behave has been in alignment with what the current implementation does for some time. I think it was in the “oh noes, patterns are interpreted as matching against existing consts or enum variants if they’re in scope, and as introducing a new binding otherwise” and later the "split static into const and static" debates where these crystallized for me.)

The only one of the described issues that really bothers me is the interaction with associated constants. I think forbidding the use of associated constants in patterns would be a prudent way to address it. Or more generally and precisely: only allow use of constants in patterns when the value of the constant is known at that location.

(I don’t really like const fns either, so I’m not sure which part of that interaction I would identify as being the problematic one, if any.)

I think PartialEq (or Eq) is a red herring here. Consider a struct (let’s call it Foo) whose fields are all public (let’s call them a and b), and which has a manual PartialEq impl which is not the same as the one which would be derived (i.e., the structural one). As a client, I can write either foo1 == foo2 or foo1.a == foo2.a && foo1.b == foo2.b, and they will have different behavior, which may surprise me. Notably, this is the same issue as described for consts, without involving consts. I think it is fundamentally an API (rather than necessarily language) design flaw to expose multiple, interchangeable, but inconsistent ways of performing equality tests on a type. I think the appropriate remedy here is to lint against (a) defining a non-structural (or any manual) PartialEq impl on a type with all-public (all-PartialEq, or all-matchable) fields, and against (b) exposing both a non-structural/manual PartialEq impl as well as consts for a type with private fields.

Matching against consts of a type with private fields also doesn’t bother me (though if there’s also a PartialEq impl, then that should be a lint on the provider’s side, as described above), any more than having a smart constructor fn for a type with private fields bothers me. I think patterns should be thought of as just the mirror image of (“dual to”) literals: both are language primitives, and more fundamental than things like PartialEq which can be built on top in user-written code. Construction and deconstruction.

I think transparent vs. opaque constants is an interesting question though, even if it’s clear that the established semantics of the consts currently in the language is transparency. An intuitive way to think about it is, “should the value of the constant appear in the generated documentation?”. There’s an important parallel with type aliases, both of which are mechanisms for abstracting over (or, at least, making shorthands for) “compile-time things”. I believe the distinction between transparent and opaque constants corresponds directly to the distinction between type and abstract type. In other words, both our current type and const are transparent, and just as with abstract type, we could also introduce abstract const, which would be an opaque constant. For MLers (which I am only barely), this corresponds to the distinction between transparent and opaque types in signatures (sig type Foo end vs. sig type Foo = Int end), except that in Rust we don’t write module signatures (“types for modules”), instead writing pub (and potentially abstract) modifiers inline in module definitions.

Finally, I think our consts also correspond to a restricted subset (no bindings, only bidirectional) of the pattern synonyms which have been semi-recently added to GHC.

2 Likes