How to solve inference issue?


#1

On this PR, which gives the SliceIndex a default implementation for R: RangeBounds<usize>, I’m running into the following error in an existing test:

[00:08:42]    Compiling syntax_ext v0.0.0 (file:///checkout/src/libsyntax_ext)
[00:08:58] error[E0282]: type annotations needed
[00:08:58]     --> librustc/hir/lowering.rs:1903:58
[00:08:58]      |
[00:08:58] 1903 |                     add_bounds.get(&ty_param.id).map_or(&[][..], |x| &x),
[00:08:58]      |                                                          ^^^^^^ cannot infer type for `T`
41760 ./obj/build/x86_64-unknown-linux-gnu/stage0-rustc/x86_64-unknown-linux-gnu
41756 ./obj/build/x86_64-unknown-linux-gnu/stage0-rustc/x86_64-unknown-linux-gnu/release
41260 ./src/llvm/test/CodeGen/X86
40776 ./src/libcompiler_builtins

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!


#3

Bump.


#4

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.


#5

@drXor

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.


#6

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.


#7

This is contravariance, not covariance though. You start with a more concret type (!), and end up with a more general type.


#8

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.


#9

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.


#10

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.