Smart cast to 'Type' is impossible, because 'variable' is a mutable property that could have been changed by this time
KotlinKotlin Problem Overview
And the Kotlin newbie asks, "why won't the following code compile?":
var left: Node? = null
fun show() {
if (left != null) {
queue.add(left) // ERROR HERE
}
}
> Smart cast to 'Node' is impossible, because 'left' is a mutable > property that could have been changed by this time
I get that left
is mutable variable, but I'm explicitly checking left != null
and left
is of type Node
so why can't it be smart-casted to that type?
How can I fix this elegantly?
Kotlin Solutions
Solution 1 - Kotlin
Between execution of left != null
and queue.add(left)
another thread could have changed the value of left
to null
.
To work around this you have several options. Here are some:
-
Use a local variable with smart cast:
val node = left if (node != null) { queue.add(node) }
-
Use a safe call such as one of the following:
left?.let { node -> queue.add(node) } left?.let { queue.add(it) } left?.let(queue::add)
-
Use the Elvis operator with
return
to return early from the enclosing function:queue.add(left ?: return)
Note that
break
andcontinue
can be used similarly for checks within loops.
Solution 2 - Kotlin
1) Also you can use lateinit
If you sure do your initialization later on onCreate()
or elsewhere.
Use this
lateinit var left: Node
Instead of this
var left: Node? = null
2) And there is other way that use !!
end of variable when you use it like this
queue.add(left!!) // add !!
Solution 3 - Kotlin
There is a fourth option in addition to the ones in mfulton26's answer.
By using the ?.
operator it is possible to call methods as well as fields without dealing with let
or using local variables.
Some code for context:
var factory: ServerSocketFactory = SSLServerSocketFactory.getDefault();
socket = factory.createServerSocket(port)
socket.close()//smartcast impossible
socket?.close()//Smartcast possible. And works when called
It works with methods, fields and all the other things I tried to get it to work.
So in order to solve the issue, instead of having to use manual casts or using local variables, you can use ?.
to call the methods.
For reference, this was tested in Kotlin 1.1.4-3
, but also tested in 1.1.51
and 1.1.60
. There's no guarantee it works on other versions, it could be a new feature.
Using the ?.
operator can't be used in your case since it's a passed variable that's the problem. The Elvis operator can be used as an alternative, and it's probably the one that requires the least amount of code. Instead of using continue
though, return
could also be used.
Using manual casting could also be an option, but this isn't null safe:
queue.add(left as Node);
Meaning if left has changed on a different thread, the program will crash.
Solution 4 - Kotlin
The practical reason why this doesn't work is not related to threads. The point is that node.left
is effectively translated into node.getLeft()
.
This property getter might be defined as:
val left get() = if (Math.random() < 0.5) null else leftPtr
Therefore two calls might not return the same result.
Solution 5 - Kotlin
Change var left: Node? = null
to lateinit var left: Node
. Problem solved.
Solution 6 - Kotlin
Your most elegant solution must be:
var left: Node? = null
fun show() {
left?.also {
queue.add( it )
}
}
Then you don't have to define a new and unnecessary local variable, and you don't have any new assertions or casts (which are not DRY). Other scope functions could also work so choose your favourite.
Solution 7 - Kotlin
Do this:
var left: Node? = null
fun show() {
val left = left
if (left != null) {
queue.add(left) // safe cast succeeds
}
}
Which seems to be the first option provided by the accepted answer, but that's what you're looking for.
Solution 8 - Kotlin
For there to be a Smart Cast of the properties, the data type of the property must be the class that contains the method or behavior that you want to access and NOT that the property is of the type of the super class.
e.g on Android
Be:
class MyVM : ViewModel() {
fun onClick() {}
}
Solution:
From: private lateinit var viewModel: ViewModel
To: private lateinit var viewModel: MyVM
Usage:
viewModel = ViewModelProvider(this)[MyVM::class.java]
viewModel.onClick {}
GL
Solution 9 - Kotlin
Try using the not-null assertion operator...
queue.add(left!!)
Solution 10 - Kotlin
How I would write it:
var left: Node? = null
fun show() {
val left = left ?: return
queue.add(left) // no error because we return if it is null
}
Solution 11 - Kotlin
Perform as below :-
var left: Node? = null
> Use a null safe call
left?.let { node -> queue.add(node) } // The most preferred one
Solution 12 - Kotlin
This worked for me: private lateinit var varName: String