| Why? It's just a property of type transformation. Assuming Parent <- Child ("<-" denotes inheritance): - If Generic<Parent> <- Generic<Child>: it's covariant. - If Generic<Parent> -> Generic<Child>: it's contravariant. - Otherwise: it's invariant. Or at least it's that straightforward in C#. Are there complications in Rust? |
Scala was the first language in my exposure to try to simplify that away by lifting the variance annotations into the type parameter directly. It reduced some of the power but it made things easier to understand for developers. A full variance model would annotate specific features (methods) of a type as co/contra/invariant.
I'm not sure what approach C# takes - I haven't looked into it.
Rust doesn't expose variances for data structures at all. It exposes them for traits (type classes) and lifetimes, but neither of those are accessible in a higher order form. Trait usage is highly constrained and so is lifetime usage.
Traits mainly can be used as bounds on types. Some subset of traits, characterized as object traits, can be used as types themselves, but only in an indirect context. These are highly restricted. For example, if you have traits T and U where U extends T, and you have a reference `&dyn U`, you can't convert that to a `&dyn T` without knowledge of the underlying concrete type. You _can_ convert `&A where A: U` to `&B where B: T`, but that just falls out of the fact that the compiler has access to all the concrete type and trait definitions there and can validate the transform.
Rust's inheritance/variance story is a bit weird. They've kept it very minimal and constrained.