Home > Back-end >  Why does if not null not work for mutable variables?
Why does if not null not work for mutable variables?

Time:10-19

This code doesn't compile.

class MyClass {
        var w: String? = "Hello"
        init {
            if(w!=null) {
                println(w.length)
            }
        }
    }

Compiler error: Smart cast to 'String' is impossible because 'w' is a mutable property that could've been changed by this time. What does this mean? A similar code compiles perfectly.

fun main(args: Array<String>) {
    var w: String? = "Hello"
    if(w!=null) {
        println(w.length)
    }
}

They are similar because, as per my understanding, in both cases, the variable w will be instantiated and the if block will run right after it. So why does this code compile perfectly?

CodePudding user response:

In general these errors are because the compiler cannot guarantee that the variable cannot be changed between the null-check and the usage that assumes it's not null. In most cases, this is because another thread could modify the variable in the meantime.

For instance, there could be some code in another init block spawning a thread that could change the value of the variable:

class MyClass {
    var w: String? = "Hello"

    init {
        thread {
            w = null
        }
    }

    init {
        if (w!=null) {
            println(w.length)
        }
    }
}

The compiler doesn't check all the code that could potentially have effects like that, so it prefers to be conservative and gives you an error.

In the case of the main method, the variable is local, so the compiler can more easily guarantee that it cannot be changed by anything else.

To fix this issue, you can create an intermediate local variable when you access your w property:

val myW = w
if (myW != null) {
    println(myW.length)
}

Or use let:

w?.let { nonNullW ->
    println(nonNullW.length)
}

CodePudding user response:

In your first block of code, w is a property. In your second block of code, w is a local variable.

A mutable property cannot be smart-cast, because the compiler cannot ensure that the value of the property will not change in between checking the type/nullability and using the value.

When you need to use a nullable property, to avoid doing a non-null assertion, you have to copy it to a local variable first. You can do this explicitly or by using a scope function.

//Explicit
val localW = w
if (localW != null) {
    println(localW.length)
}

// The common ?.let pattern
w?.let { println(it.length) }

// run with function reference
w?.length?.run(::println)
  • Related