kotlingenerics

Why is the Unit type treated in a special way in Kotlin?


Please explain this behavior in Kotlin:

data class A<T>(val p: T)
class B

fun f1(): A<Number> = A(B()) as A<Number>
fun f2(): A<Unit> = A(B()) as A<Unit>

fun main() {
    println("f1: ${f1().p::class.simpleName}") // print "f1: B"
    println("f2: ${f2().p::class.simpleName}") // print "f2: Unit"
}

I would expect the output of "B" in both the first and second cases.

Also, interestingly, its work correctly for data class A<T>(val p: T?).

Is it some kind of compiler optimization?


Solution

  • When return type is declared as Unit, Kotlin applies specific behavior, it coerces the result to Unit ignoring the actual return value.
    It is stated in Kotlin specs: Coercion to kotlin.Unit.

    In this line fun f2(): A<Unit> = A(B()) as A<Unit>, it is declared that the type of p has to be Unit. So Kotlin discards the actual return value of f2().p and coerces return value to Unit.

    The same behavior applies to functions with an explicitly declared return type of Unit

    // compiled and returns Unit
    fun compiledFun(): Unit = runBlocking {
        "String"
    }
    
    // compilation exception: Argument type mismatch: actual type is 'kotlin.String', but 'kotlin.Int' was expected.
    fun notCompiledFun(): Int = runBlocking {
        "String"
    }
    

    This behavior is often used when writing JUnit tests, because JUnit does not run test methods unless their return type is Unit. Similar to Java, where JUnit does not run test methods unless their return is void.

    @Test
    fun `JUnit runs me`(): Unit = runBlocking {
        assertEquals(4, 2 + 2)
        "Done"
    }
    
    @Test
    fun `JUnit does NOT run me`() = runBlocking {
        assertEquals(4, 2 + 2)
        "Done"
    }
    

    Regarding case data class A<T>(val p: T?):
    The type of p is Unit? in case of A(B()) as A<Unit>, and I guess Kotlin distinguishes between Unit and Unit? and therefore does not apply the coercion logic.