Search code examples
kotlinnullkotlin-null-safety

Nullable var and smart cast


Consider the following block of Kotlin.

var nullableInt: Int? = null

if (nullableInt != null) {
    val checkedInt: Int = nullableInt
    print("not-null-branch")
} else {
    print("null-branch")
}

Android Studio tells me that the smart cast from Int? to Int is not possible, since nullableInt is mutable. I understand that this may be a problem in multithreaded code.

One way to handle the problem is to make an explicit cast with val checkedInt: Int = nullableInt!!, but if I would use the coded in a multithread environment that is not advisable.


Close duplicates

There are a couple of very close questions on SO regarding this topic. However, I don't find a satistafctory answer in any of the ones I've found:

In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them discusses why the problem arises, but provides no suggestion on how to handle it

Kotlin avoid smart cast for null check has a if-not-null branch that returns a non-null value, so the ?.let{} :? {} construct works there. Since my not-null-branch returns null, both branches would run.

Kotlin "Smart cast is impossible, because the property could have been changed by this time" concerns only with a not-null-branch and no null-branch, so the ?.let{} construct seems correct. In that thread, they provide the suggestion about taking a local copy before the if statement, which could be doable in my case too. It is unfortunately not very elegant, and I hope there is some other alternative.


Is there any way to handle this null-conditional branching in a null safe manner without taking a copy?

I understand that the answer potentially could be "it depends". If that is the case, please say so and elaborate on why.


Solution

  • Use .let instead of ?.let

    Because the .let extension function is defined for all types, including nullable ones, you can actually call it without the safe-call ?. operator. When you do that, the lambda will always be called, even for null values. The parameter inside the let block will be nullable if the receiver is nullable.

    However, the lambda parameter is eligible for smart casting, because it isn't mutable.

    Here's the difference:

    x.let { it -> /* This always runs. 'it' can be null if 'x' is null */ }
    x?.let { it -> /* This only runs if 'x' is not null. 'it' is never null. */ }
    

    Applying that to your example, you could write this:

    var nullableInt: Int? = null
    
    nullableInt.let {
        if (it != null) {
             doSomethingWith(it)
        } else {
             doSomethingElse()
        }
    }