Parametricity and the "associated type workaround": Why is this ok?


#1

Why is this forbidden:

struct Forbidden<T:Trait>(i32);

while this is cool beans:

struct CoolBeans<T:Trait>(T::Cheat);

considering that the latter conveys strictly less information?

// I could have written:
struct CoolBeans<T:Trait>(T::Cheat);

trait Super { type Cheat; }
trait Trait: Super<Cheat=i32> { }

#2

I don’t think it has anything to do with parametricity. The type parameter needs to be used so the compiler can understand what constraints it places on usage of the struct.


#3

That is along the lines of what I’ve heard before; but given my third example, I am not convinced that my usage of T::Cheat actually establishes anything about the constraints on T.


#4

It doesn’t establish much in the generic definition but compiler will be able to understand variance of your type once concrete types are created because the struct contains a field of the associated type.


#5

Amazing what one can accomplish with a little bit of ripgrep in the rustc source:

#![feature(rustc_attrs)]

#[rustc_variance]
struct NotCool<T:Trait>(i32);

#[rustc_variance]
struct OkeyDokes<T:Trait>(T::Cheat);

trait Super { type Cheat; }
trait Trait: Super<Cheat=i32> { }
error[E0208]: [*]
 --> src/lib.rs:4:1
  |
4 | struct NotCool<T:Trait>(i32);
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0208]: [o]
 --> src/lib.rs:7:1
  |
7 | struct OkeyDokes<T:Trait>(T::Cheat);
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This means that the compiler has classified NotCool as bivariant with respect to T, while it has classified OkeyDokes as invariant.

…which I assume means something to somebody, but alas, ripgrep cannot cure plain ignorance.

EDIT: I suppose that OkeyDokes is soundly allowed simply because the compiler makes the most conservative classification possible? If so, why doesn’t it always do this?