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

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> { }
1 Like

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.

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.

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.

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?