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.

Type projection in Kotlin is a use-site variance mechanism that restricts the type signature of a generic class or interface at the point of its usage. By applying variance modifiers (in or out) or the star projection (*) to type arguments in variable declarations or function parameters, developers can safely accept generic types with differing type parameters, effectively overriding the generic type’s default invariance. Unlike declaration-site variance (where modifiers are applied to the class definition itself, e.g., class Box<out T>), type projection occurs where the type is instantiated or referenced.

Covariant Projection (out)

Applying the out modifier at the use-site creates a covariant projection. It restricts the generic object to act strictly as a producer.
  • Mechanics: The compiler allows calling methods that return the generic type T, but prohibits calling any methods that take T as a parameter.
  • Type Safety: It guarantees that the object will only emit instances of T (or its subtypes) and prevents the injection of incompatible types.
class MutableReference<T>(var value: T)

// The parameter is projected covariantly
fun readOnly(ref: MutableReference<out Number>) {
    val n: Number = ref.value // ALLOWED: Reading is safe
    // ref.value = 42         // ERROR: Setter expects 'Nothing', cannot write
}

Contravariant Projection (in)

Applying the in modifier at the use-site creates a contravariant projection. It restricts the generic object to act strictly as a consumer.
  • Mechanics: The compiler allows calling methods that take the generic type T as a parameter, but restricts the type returned when reading from the object.
  • Type Safety: It guarantees that the object can safely consume instances of T (or its subtypes). Because the exact type argument passed to the generic class is unknown at the use-site, the compiler cannot safely return T. Instead, reading from an in-projected type returns the declared upper bound of the generic type parameter. If no explicit upper bound is defined at the declaration site, it defaults to Any?.
class BoundedReference<T : Number>(var value: T)

// The parameters are projected contravariantly
fun writeOnly(ref: MutableReference<in Number>, boundedRef: BoundedReference<in Int>) {
    ref.value = 42             // ALLOWED: Writing is safe
    boundedRef.value = 10      // ALLOWED: Writing is safe
    
    // Reading returns the declaration-site upper bound, not the exact type argument
    val a: Any? = ref.value          // ALLOWED: Upper bound of MutableReference<T> is Any?
    val b: Number = boundedRef.value // ALLOWED: Upper bound of BoundedReference<T : Number> is Number
    
    // val n: Number = ref.value     // ERROR: Returns 'Any?', not 'Number'
    // val i: Int = boundedRef.value // ERROR: Returns 'Number', not 'Int'
}

Star Projection (*)

Star projection is used when the exact type argument is unknown or irrelevant, but type safety must still be maintained. It provides a safe approximation of the generic type based on its declaration.
  • Mechanics:
    • For reading (producing), Foo<*> acts like Foo<out TUpper>, where TUpper is the upper bound of the generic parameter (defaults to Any?).
    • For writing (consuming), Foo<*> acts like Foo<in Nothing>, meaning you cannot write any value to it.
  • Type Safety: It prevents unsafe writes entirely while allowing safe, generalized reads.
// The parameter uses star projection
fun inspect(ref: MutableReference<*>) {
    val a: Any? = ref.value    // ALLOWED: Reads as 'out Any?'
    // ref.value = "String"    // ERROR: Writes as 'in Nothing'
}

Type Projection vs. Invariance

By default, generic types in Kotlin are invariant. Type projection explicitly narrows the capabilities of the type to achieve subtyping flexibility.
// Invariant usage (No projection)
fun process(ref: MutableReference<Number>) {
    val n: Number = ref.value  // ALLOWED
    ref.value = 42             // ALLOWED
}

val intRef = MutableReference<Int>(10)

// process(intRef)             // ERROR: MutableReference<Int> is not MutableReference<Number>
// readOnly(intRef)            // ALLOWED: MutableReference<Int> is a subtype of MutableReference<out Number>
Master Kotlin with Deep Grasping Methodology!Learn More