Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Contravariance is a generic type system feature that reverses the standard subtyping relationship between parameterized types. In Kotlin, if type Sub is a subtype of type Super, a contravariant generic type Generic<T> establishes that Generic<Super> is a subtype of Generic<Sub>. This allows a generic instance typed to a superclass to be safely assigned to a reference typed to a subclass. Kotlin implements contravariance using the in variance annotation. This modifier restricts the generic type parameter so that it can only be consumed (passed as input arguments to functions) and never produced (returned from functions or exposed as public properties).

Declaration-Site Variance

When the in modifier is applied at the class or interface declaration, it is known as declaration-site variance. The Kotlin compiler enforces the “in-position” restriction across the entire type.
interface Consumer<in T> {
    // Valid: T is in an input position
    fun consume(item: T) 
    
    // Compilation Error: Type parameter T is declared as 'in'
    // and cannot occur in an 'out' position.
    // fun produce(): T 
}

Subtyping Mechanics

The reversal of the subtyping relationship is the core mechanic of contravariance. Because a Consumer<Super> can handle any instance of Super, it is logically safe to treat it as a Consumer<Sub>, since Sub is guaranteed to possess all the traits of Super.
open class Animal
class Dog : Animal()
class Cat : Animal()

interface Handler<in T> {
    fun handle(item: T)
}

// Instantiate a handler for the supertype
val animalHandler: Handler<Animal> = object : Handler<Animal> {
    override fun handle(item: Animal) {
        println(item)
    }
}

// Valid: Handler<Animal> is a subtype of Handler<Dog>
val dogHandler: Handler<Dog> = animalHandler

// The underlying animalHandler safely consumes a Dog, 
// because a Dog is an Animal.
dogHandler.handle(Dog()) 

Type Safety Enforcement

The compiler strictly enforces the in restriction to prevent runtime ClassCastExceptions. If a contravariant type were permitted to return T, type safety would collapse. If Handler<in T> allowed a function fun get(): T, the following invalid state would compile:
  1. animalHandler is assigned to dogHandler.
  2. animalHandler.get() internally returns a Cat (which is valid for Handler<Animal>).
  3. dogHandler.get() expects a Dog, but receives the Cat from the underlying animalHandler.
By restricting T to input positions, the compiler guarantees that the generic type only ever receives data it is equipped to handle, and never promises to return data of a specific subclass.

Use-Site Variance (Type Projections)

If a generic class is invariant (declared without in or out), contravariance can be applied at the point of usage. This is called a type projection. It restricts the API of the invariant class for that specific reference, hiding any methods where T appears in an out position.
// Invariant class
class Box<T>(var item: T) 

// Use-site contravariance
fun putString(box: Box<in String>, value: String) {
    // Valid: We can write to the box because String is expected
    box.item = value 
    
    // Compilation Error: Type mismatch. 
    // The compiler only knows the item is Any?, not String.
    // val retrieved: String = box.item 
}

val anyBox = Box<Any>("Initial")
// Valid: Box<Any> is projected as Box<in String>
putString(anyBox, "New String") 
Master Kotlin with Deep Grasping Methodology!Learn More