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