Before the PR, SliceIndex was implemented for a number of concrete types. With this PR, it is given a default impl for R: RangeBounds<usize>, and the concrete impls now specialize that default impl. I suspect it may be an instance of this issue.
Does anyone have an idea of how I might go about fixing this? Is it even fixable with the current behavior of impl specialization? Thanks!
So, this reminds me of an issue I’ve had before where I use an empty array literal and get bizarre typing errors like this.
When I look at [] in a vacuum, I want it to type as [!; 0] and treat it as coercible to any empty array type. The problem, of course, is that [T; n] is invariant in T… except when n is zero, and then it can safely be treated as covariant and contravariant, since [T; 0] is the “same” type for all types T, since all of them have the same layout and index always panics. I think treating [T; 0] as covariant and having [] always type as [!; 0] would probably solve most weird typing issues with []. I don’t think the variance of a type can depend on a parameter though, so this would probably require some fiddling.
The problem is of cause, if you want [T] can be mutable, it have to be invariant. It can be covariant only when you are sure it is read only. So, you are proberly wanted an empty Iterator<!>, and yes it can be treated as any empty iterators.
If [T] were covariant, people can then put Banana into a Apple blasket because it was only known to be a Fruit blasket.
I’m aware of how mutual insertion and access imply invariance- I’ve had to deal with languages which make the mistake of making their Vec covariant (see: Java).
But that doesn’t matter. If you coerce a [T; 0] to [T], you lose covariance. It is completely sound to start with a [!; 0], coerce to [T; 0], and end up with an &mut [T], since you can’t produce &mut Ts from it anyways.
Not quite. A covariant type constructor K<_>: type -> type is such that if T <: U, then K<T> <: K<U>. By Rust's coercion rules, if T <: U, then an expression that types as K<T> may be coerced to K<U>. Rust doesn't have inheritance, so the main places people care about are
&mut T <: &T
T <: dyn Trait
[T; _] <: [T]
! <: T
For example, because &T is covariant, &! may be coerced to any &T you want, though &! is impossible to obtain without invoking UB along the way:
(&0 as *const _ as *const !).as_ref().unwrap()
Now, it is kosher for [!; 0] to be contravariant, by the same argument for covariance. I don't know that this is useful though. Now you get to convert any [T; 0] to any [U; 0]... which introduces unnecessary special-casing in the compiler, and which could be achieved by declaring in the nomicon that
transmute::<[T;0], [U;0]>(..)
is never UB. Since the point of making [T; 0] covariant in T is to allow for [] to have a default type without introducing an unnameable [?; 0] type, saying "just allow this transmute and don't mess with current variance" defeats the point.
Unfortunately, changing the semantics of [] isn’t an option here - I can only change my implementation of the SliceIndex trait. What confuses me is that, in the impl that defines the default methods, it specifies type Output = [T], which is not a default. So, I figure, Rust should be able to figure out that, regardless of which specialization is used, the output type is always [T]. That’s why I’m confused that, considering the test in question worked properly (wrt inference) before, it fails now. I’d expect it to still succeed.
Yeah, I wasn’t proposing a short-term solution for your problem; you’re probably best off using some kind of identity function with explicit turbofish to annotate the type.
Hopefully someone with specialization chops can show up and help you out.