Covariance / Contravariance

Hello everybody, I was kind of experimenting with covariance and contravariance in Swift to better understand this topic, and I run into something I can't understand, maybe I'm missing something here.

Suppose we have:

class Foo<T> where T : Bar {} 
class Bar {}
class BarChild : Bar {}

let array: Array<Bar> = Array<BarChild>()

The above assignment compiles fine, and thus I may assume that Swift types are contravariant in their generic signature, but then I tried this:

let instance: Foo<Bar> = Foo<BarChild>()

I expected the last assignment to work as the other one, but it doesn't, why is that?
Are just standard library types able to be contravariant in their generic types?

I then tried:

class FooChild<C> : Foo<C> where C : Bar
let childInFather: Foo<Bar> = FooChild<Bar>; Ok

For the sake of brevity, this is the only assignment working (reversing Child - Father relationship in both enclosing and generic types don't compile), hence I assume that Swift generic types, when of reference kind (classes), are invariant in their generic argument but contravariant in their container type (the holder of the generic argument to be clear).

If I got something wrong please feel free to correct me, just seeking for somebody more educated than me :slight_smile:

3 Likes

Yep.

1 Like

As Jayton said, some of the standard library types are special cases and don't behave like most other types.

If you want to understand complex relationships between types, I would recommend checking out awesome document swift/DynamicCasting.md at main · apple/swift · GitHub written by @tbkka last week which is a description how dynamic cast operators (as? as! is) behave in swift today. It talks about dynamic casts, and not static casts like the one you're interested in, but should highlight some sharper edges.

4 Likes

Interesting, thank you

1 Like

Swift has special case support for a few standard library types (Optional, Array, Set, Dictionary). But it would require language changes to expand that to support user-defined types.

In particular, the compiler currently is not able to tell whether your Foo<> type is actually covariant in its generic type. (Not all generic types are; it depends on how your Foo type uses the generic argument.)

3 Likes