(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 const
s 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 fn
s 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 derive
d (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 const
s, without involving const
s. 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-match
able) fields, and against (b) exposing both a non-structural/manual PartialEq
impl as well as const
s for a type with private fields.
Matching against const
s 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 const
s 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 const
s also correspond to a restricted subset (no bindings, only bidirectional) of the pattern synonyms which have been semi-recently added to GHC.