frankelot
frankelot

Reputation: 14409

Smart cast to 'Type' is impossible, because 'variable' is a mutable property that could have been changed by this time

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?

Upvotes: 432

Views: 190127

Answers (13)

grexlort
grexlort

Reputation: 925

For future viewers, especially Kotlin newcomers with js/ts background

Since this is the most popular question about Smart cast to 'Type' is impossible problems, I would like to elaborate on what did brought me here. My case was to simply transform nullable var instance into the another class instance but I was getting errors like this one: Smart cast to 'TestClass' is impossible, because 'testObject' is a mutable property that could have been changed by this time

data class TestClass(
  val x: String,
  val y: String
)

data class FooClass(
  val x: String,
  val y: String
)

var testObject: Testclass? = Testclass("x", "y")

val transformed = testObject?.let { FooClass(x = testObject.x, y = testObject.y) }

and I didn't really understand what was wrong, since I've used safe call operator and everything was looking fine!

But then I realized, Kotlin in comparison to JS is multi-threaded, so in theory a mutable variable could be changed, and then I realized my mistake - I used testObject variable directly instead of using it

val transformed = testObject?.let { FooClass(x = testObject.x, y = testObject.y) }

so the correct solution is

val transformed = testObject?.let { FooClass(x = it.x, y = it.y) }

Upvotes: 1

Radesh
Radesh

Reputation: 13555

1) Also you can use lateinit If you are sure you will 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 !!

Upvotes: 75

mfulton26
mfulton26

Reputation: 31214

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:

  1. Use a local variable with smart cast:

     val node = left
     if (node != null) {
         queue.add(node)
     }
    
  2. 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)
    
  3. Use the Elvis operator with return to return early from the enclosing function:

     queue.add(left ?: return)
    

    Note that break and continue can be used similarly for checks within loops.

Upvotes: 522

Perform as below :-

var left: Node? = null

Use a null safe call

left?.let { node -> queue.add(node) } // The most preferred one

Upvotes: 0

Leonard
Leonard

Reputation: 205

This worked for me: private lateinit var varName: String

Upvotes: -2

Simon Jacobs
Simon Jacobs

Reputation: 6463

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.

Upvotes: 4

tonisives
tonisives

Reputation: 2510

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
}

Upvotes: 0

Braian Coronel
Braian Coronel

Reputation: 22867

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

Upvotes: 0

Mohammed mansoor
Mohammed mansoor

Reputation: 819

Change var left: Node? = null to lateinit var left: Node. Problem solved.

Upvotes: 7

Roland Illig
Roland Illig

Reputation: 41617

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.

Upvotes: 7

Bikeboy
Bikeboy

Reputation: 553

Try using the not-null assertion operator...

queue.add(left!!) 

Upvotes: 0

EpicPandaForce
EpicPandaForce

Reputation: 81529

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.

Upvotes: 1

Zoe - Save the data dump
Zoe - Save the data dump

Reputation: 28229

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.

Upvotes: 33

Related Questions