Spawned off of investigation of issue #62307 and PR #55837
We currently allow constants that are empty arrays of any type to be used as patterns in match. We allow this regardless of whether they are an array of an ADT that does not derive PartialEq/Eq
This may be a feature or a bug depending on one's perspective.
Here is an example of the behavior in question (play):
struct B(i32);
const B0: [B; 0] = [];
//#[derive(PartialEq, Eq)] // can't uncomment b/c B doesn't impl PartialEq+Eq.
struct UhOh([B; 0]);
const _UH_OH: UhOh = UhOh(B0);
fn main() {
match [] {
B0 => { println!("B0 matched []"); }
}
match UhOh([]) {
UhOh(B0) => { println!("UhOh(B0) matched UhOh([])"); }
}
#[cfg(this_wont_compile_without_derive_of_partial_eq_and_eq)]
match UhOh([]) {
_UH_OH => { println!("_UH_OH matched UhOh([]])"); }
}
}
To be clear: This behavior might be fine.
It is just a little weird, because on other uses of consts in patterns, we do tend to require that their types derive PartialEq and Eq
#[structural_match]; see rust-lang/rfcs#1445).PartialEq is not required for a const in a pattern, namely for for <'a> fn(&'a T); see https://github.com/rust-lang/rust/issues/46989#issuecomment-353805481 )But we can treat an empty array as an exceptional case here, if that's what people want.
nominating for discussion at T-lang team meeting, since this is a question about the design of the language itself.
(It may be worthwhile to try to make a PR that errors on such consts, e.g. rejecting the match [] { B0 => ... } in the example above, and then doing a crater run against that PR, to gather data on whether this is likely to occur in the wild.)
A possibly interesting note: I think the reason this case wasn't caught was because of how this is written:
because we have layered the type-checking atop the recursion (encoded in the adt_subpattern closure), and there is no substructure to recur on when n=0.
I mainly note this because I'm curious if there's other checks on the contents' type in [T; n] that are skipped because of similar folding over 0..n.
This is consistent with special handling for zero-length arrays throughout the language. For instance, Default is implemented for zero-length arrays, even for types that themselves don't implement Default. (I think this is the right decision, but it does make some behaviour more complex.)
Further to @varkor's point, another case where LEN = 0 is "special" (or rather <= 1 is special for expected move-checking reasons) is the case of array repeat expressions, e.g.
struct NoCopy;
let x = [NoCopy; 0]; // OK.
let x = [NoCopy; 1]; // OK.
let x = [NoCopy; 2]; // ERROR.
And yet the type [NoCopy; 0] is itself non-copyable... right? (play).
seems like something that's been decided in an ad hoc manner in each case, rather than having a consistent policy, no?
@pnkfelix A very good point! It seems pretty inconsistent indeed.
Might be good to do a survey if time allows. Ostensibly making [NoCopy; 0] Copy is the non-breaking change in "what direction should we move consistency in" discussion.
One further case in which [T; 0] is special is if is_uninhabited(T) holds and the corollary to this: [T; 0] is always a singleton type (if we assume totality under a sort of fast and loose reasoning).
Discussed briefly in the @rust-lang/lang meeting. We need to schedule some time to get into structural match. But, in the meantime, a useful step would be to do a crater run that tries to remove the [Foo; 0] case and see whether it has any effect.
I don't know @pnkfelix whether you'd have time to do that before you leave, but for anyone else who might have an interest, you could take a look at https://github.com/rust-lang/rust/pull/62339 which adjusted this code and try to build a test PR from that.
Most helpful comment
This is consistent with special handling for zero-length arrays throughout the language. For instance,
Defaultis implemented for zero-length arrays, even for types that themselves don't implementDefault. (I think this is the right decision, but it does make some behaviour more complex.)