LudvigH
LudvigH

Reputation: 4800

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.

Upvotes: 2

Views: 1447

Answers (2)

Tenfour04
Tenfour04

Reputation: 93882

You can’t start-cast a property because another thread could be modifying it. There is no logical way around that. The language already provides an unsafe way to do it with the !! that you already mentioned.

Making a local copy of the reference (either manually or by using a scope function like with or let) is trivial so it’s not something you need to worry about.

My personal opinion is that a local variable is the cleanest, most readable way to do it. You avoid the nesting of blocks you’d have with scope functions.

val myVar = myProp
if (myVar != null) {

} else {

}

I think the cleanest way with scope functions is to use with. It reads better than let when you’re handling both branches.

with(myProp) {
    if (this != null) {

    } else {

    }
}

For a concise way to handle both branches, the following works. I consider also to be a little more robust than let for this situation, because you can’t accidentally run both branches by returning a null from the first lambda. But the readability suffers from going this concise.

myProp?.also {
    // not null it
} ?: run {
    // null
}

Upvotes: 3

Sam
Sam

Reputation: 9996

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()
    }
}

Upvotes: 4

Related Questions