Search code examples
kotlingenericscontravarianceunbounded-wildcard

Is there an exception in which it's possible to read a contravariance (in kotlin)?


I was told that it could be possible to read a contravariance if used "Any?" as in

fun processList(list: List<in Class1>) {
    val item: Any? = list[0] 
    println(item)
}

To test out, I used:

open class Class2

open class Class1 : Class2()

fun main() {
    val list2: List<Class2> =listOf(Class2(), Class2())
   val list: List<in Class1> = list2 
    val item: Any? = list[0] 
}

But I doubt this information because I get the error

Projection is conflicting with variance of the corresponding type parameter of 'kotlin.collections.List<in Class1>'. Remove the projection or replace it with '*'.

Is reading not possible at all? Besides, it suggests that I use "*", but why an unbounded wildcard would allow me to read? Shouldn't it have the same restrictions as both covariance and contravariance (not being able to produce nor consume)?


Solution

  • List<in Class1> itself is simply not a valid type, let alone whether you can read from it.

    The type parameter of List is covariant:

    interface List<out E> : Collection<E> 
    

    List<in Class1> contradicts this variance, so is not allowed.

    From the language spec,

    Kotlin prohibits contradictory combinations of declaration- and use-site variance as follows.

    • It is a compile-time error to use a covariant type argument in a contravariant type parameter
    • It is a compile-time error to use a contravariant type argument in a covariant type parameter

    There is no technical reason for this restriction. This is just to prevent you from doing writing code that doesn't make sense. By using Foo<in T>, you are expressing that this Foo should be a consumer of T, but if the type parameter is covariant at the declaration site, that means Foo has no way of consuming anything. Clearly, something has gone wrong, and the compiler error is there to tell you that.

    If you replace List with another type with a non-covariant type parameter, it works as expected. You can successfully get a Any? from a MutableList<in Class1> for example,

    val list2: MutableList<Class2> = mutableListOf(Class2(), Class2())
    val list: MutableList<in Class1> = list2
    val item: Any? = list[0]
    

    Star projection doesn't have both the restrictions of covariance and contravariance. Such a type would be rather useless, don't you think? It cannot consume anything nor produce anything (regarding its type parameter, at least).

    The documentation clearly explains what a star projection means. To paraphrase, if the declaration site variance is invariant, * acts like out when you use it as a producer, and acts like in Nothing when you use it as a consumer. Otherwise, * will be equivalent to the declaration-site variance.

    List is covariant, so List<*> just means out Any?.