Search code examples
kotlinlanguage-designkotlin-null-safety

Why does Kotlin's null safety not work properly with local variable initializers?


Please look at this code:

fun localVarNullSafety1(){
    var number: Double? = 3.0

    val sum = 2.0 + number // does not compile (Type mismatch: inferred type is Double? but Double was expected)
}

fun localVarNullSafety1(){
    var number: Double? = null
    
    number = 3.0

    val sum = 2.0 + number // compiles fine
}

I think that the above code consists of semantically identical functions - local variables don't go outside the scope, and they are local to the executing thread. In my opinion there is no reason for the first function not to compile.

I think that Kotlin's smart cast should take variable initialization into account.

Do I miss something obvious?


Solution

  • This was discussed in KT-13663, and it remains an open ticket at the time of writing.

    They were considering the more general case of (Note that T? is a supertype of T):

     val x: Supertype = Subtype()
     var y: Supertype = Subtype()
    

    and decided that in the val case, it doesn't make sense for x to have the type Subtype here, otherwise you could just not write the type annotation in the first place.

    This could make sense for vars though, but if smart casts are only implemented for vars, then vars and vals would have inconsistent behaviour regarding their types:

    val x: Supertype = Subtype()
    var y: Supertype = Subtype()
    
    // inconsistent:
    x.someSubtypeStuff() // doesn't work, x is a Supertype
    y.someSubtypeStuff() // works, y is smart casted to Subtype
    

    And so they ended up not implementing smart casts for initialisation back then.

    Since the ticket is still open, this feature may very well be implemented in a future version of Kotlin. It might support just vars, both vars and vals, or just nullable types. Let's hope for the best!