Skip to content

Variance

Note: This is an advanced section. Feel free to skip it initially.

Variance deals with the question, which generic types are compatible with each other. We explain this concept using the following class:

class Stack<T>(initialElements: List<T>) {
    fun push(element: T)

    fun pop() -> element: T
}

The class is called Stack and has a single type parameter, which is supposed to the denote the type of the elements of the stack. With its [constructor], we can specify the initial elements of the stack. Moreover, two methods are defined on the stack:

  • push is supposed to add a new element to the top of the stack.
  • pop is supposed to remove and return the topmost element of the stack.

We will now try to answer the following two questions:

  1. If A is a subclass of B, can we assign Stack<A> to Stack<B>?
  2. If B is a subclass of A, can we assign Stack<A> to Stack<B>?

Invariance

By default, the answer to both questions is "no". The reason for this is that it can allow illegal behavior:

Say, we expect a Stack<Number>, but pass a Stack<Int> (Int is a subclass of Number). If we can treat the Stack<Int> as a Stack<Number>, we are also allowed to add values of type Float to it. This would also change the original Stack<Int>, which now contains illegal floating point values.

Now, imagine we a Stack<Number>, but pass a Stack<Any> (Number is a subclass of Any). If we can treat the Stack<Any> as a Stack<Number>, we could read values from the stack that are not numbers, for example strings, since the original stack can contain Any value.

To sum this up, we cannot assign Stack<A> to Stack<B> if A is a subclass of B because we might write to the Stack<B> and alter the original Stack<A> in an illegal way. Likewise, we cannot assign Stack<A> to Stack<B> if B is a subclass of A because we might read something from the Stack<B> that is not of type B.

We say, the type parameter T of the class is invariant. It must be matched exactly. The conditions we describe above, however, already give us the information under which circumstances we can loosen this requirement.

Covariance

We now want a Stack<A> to be assignable to a Stack<B> if A is a subclass of B. This behavior is called covariance since the type compatibility relation between Stack<A> and Stack<B> points in the same direction as the type compatibility relation between A and B.

As outlined above, we can only allow covariance if we forbid writing access. This means that a type parameter that is covariant can only be used for reading. Concretely, a covariant type parameter can only be used as the type of a result not the type of a parameter. We also say the type parameter can only be used in the out-position (i.e. as output), which motivates the keyword out to denote covariance (see Section Specifying Variance).

In the Stack example, we can make the class covariant by adding the keyword out to the type parameter T and removing the writing method push:

class Stack<out T> {
    fun pop() -> element: T
}

Contravariance

We now want a Stack<A> to be assignable to a Stack<B> if B is a subclass of A. This behavior is called contravariance since the type compatibility relation between Stack<A> and Stack<B> points in the opposite direction as the type compatibility relation between A and B.

As outlined above, we can only allow contravariance if we forbid reading access. This means that a type parameter that is contravariant can only be used for writing. Concretely, a contravariant type parameter can only be used as the type of a parameter not the type of a result. We also say the type parameter can only be used in the in-position (i.e. as input), which motivates the keyword in to denote contravariance (see Section Specifying Variance).

In the Stack example, we can make the class contravariant by adding the keyword in to the type parameter T and removing the reading method pop:

class Stack<in T> {
    fun push(element: T)
}

Specifying Variance

The variance of a type parameter can only be declared at its declaration site, using the syntax shown in the following table:

Desired Variance Declaration Site
Invariant class Stack<T>
Covariant class Stack<out T>
Contravariant class Stack<in T>