Reputation: 4800
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.
Upvotes: 2
Views: 1447
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
Reputation: 9996
.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