How to solve inference issue?

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!

Bump.

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.

@mcy

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.

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

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.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.