Variance with in and out

The Problem

String is a subtype of Any, but is Box<String> a subtype of Box<Any>? By default no (generics are invariant), because someone could put an Int into a box that should only hold Strings. Variance modifiers tell the compiler when it is safe.

ModifierNameT is used only as...SubtypingExample
out Tcovariant (producer)output (return values)Source<String> is a Source<Any>List<out E>
in Tcontravariant (consumer)input (parameters)Sink<Any> is a Sink<String>Comparable<in T>
(none)invariantbothno relationMutableList<E>

out: Producers

Example

Kotlin's read-only List is declared List<out E>, so a List<Int> can be passed where List<Number> is expected.

interface Producer<out T> {
    fun produce(): T          // T only comes out
}

class OrderIdProducer : Producer<String> {
    private var n = 0
    override fun produce() = "ORD-" + (++n)
}

fun printNext(p: Producer<Any>) = println(p.produce())

fun sumAll(nums: List<Number>) = nums.sumOf { it.toDouble() }

fun main() {
    printNext(OrderIdProducer())                 // Producer<String> accepted as Producer<Any>
    val ints: List<Int> = listOf(1, 2, 3)
    println(sumAll(ints))                         // List<Int> accepted as List<Number>
}

Output

ORD-1
6.0

in: Consumers

Example

A consumer of Any can safely consume Strings, so with in the subtyping goes the other way.

fun interface Validator<in T> {
    fun isValid(value: T): Boolean    // T only goes in
}

fun checkName(name: String, v: Validator<String>) = println("'$name' valid? ${v.isValid(name)}")

fun main() {
    val notNullish: Validator<Any> = Validator { it.toString().isNotBlank() }
    checkName("Asha", notNullish)       // Validator<Any> accepted as Validator<String>
    checkName("   ", notNullish)
}

Output

'Asha' valid? true
'   ' valid? false

Tip: Memory trick: out = Output = Producer; in = Input = Consumer.