Search code examples
kotlinkotlin-extensionkotlin-null-safety

Kotlin "let{}" Doesn't Provide Smart Cast


Just learned Kotlin Nullable type and let{} function which replaces the if (xx != null) {} operation.

But one thing I am confused is that, we all know and I Think the Complier Should Know that when we use let{}, the variable/object who is calling this function is possiblly null, however the complier still requires me to add the safe call operator "?" after the variable name instead of providing Smart Cast like it does in if (xx != null) {}. Why?

My piece of code:

fun main() {
    var number1: Int? = null

    //val number2 = number1.let { it + 1 } ?: 10    //doesn't work, not quite "smart"
    val number2 = number1?.let { it + 1 } ?: 10     //works, must have "?"

    println(number1)
    println(number2)
}

Solution

  • You've already got answers in the comments, but just to explain the ? thing...

    Kotlin lets you make null-safe calls on nullable variables and properties, by adding ? before the call. You can chain this too, by doing

    nullableObject?.someProperty?.someFunction()
    

    which evaluates nullableObject, and if it's non-null it evaluates the next bit, otherwise the whole expression evaluates to null. If any part of the chain evaluates as null, the whole expression returns null.

    So it has this short-circuiting effect, and you can use the elvis "if null" operator to create a default value if you can't evaluate the whole chain to a non-null result:

    nullableObject?.nullableProperty?.someFunction() ?: defaultAction()
    

    and once you introduce the null check in the chain, you have to add it for every call after that - it's basically propagating either the result of the previous bit, or the null it resolved to, so there's a null check at each step


    The let block is just a scope function - you use it on a value, so you can run some code either using that value as a parameter or a receiver (a variable or this basically). It also has the side effect of creating a new temporary local variable holding that value, so if the original is a var it doesn't matter if that value changes, because your let code isn't referring to that variable anymore.

    So it's useful for doing null checks one time, without worrying the underlying value could become null while you're doing stuff with it:

    nullableVar?.let { it.definitelyIsNotNull() }
    

    and the compiler will recognise that and smart cast it to a non-null type. An if (nullableVar != null) check can't guarantee that nullableVar won't be null by the time the next line is executed.