Rationale behind avoiding Smart cast for mutable properties

Hi everybody.

As most of you might know, Kotlin enforces you to make local copies of mutable properties in order to gain deterministic null-safety (or overrule the nullability with ?. or !!.). In multi-threaded environments or in special code flows properties might easily change from not null to null between the check and actual usage.

Take this small example which causes the error

class Test {
    var x:String? = "Hello World"
    fun test() {
        if(this.x != null) {
            doSomething(this.x)
        }
    }
    fun doSomething(x:String) {
        println(x)
    }
}

After reading on various discussions about Kotlin, it seems the devs and language-designers of Kotlin stand behind the decision of reporting those errors and do not even want to provide an opt-out for that as “you can never be fully safe”.

The guys from Roslyn (.net compiler) are working currently on introducing nullable-reference-types to the languages. So far they do not consider mutable properties in their nullability checks that’s why I submitted a feature request to opt-in for those kind of checks. The discussions there are going into a direction that this whole behavior is just noise and annoyance because thread safety must be guaranteed differently by the devs and having those kind of checks are not reasonable at all.

As a developer I am now seriously confused as 2 big players in programming languages claim something very contradictive. The Roslyn guys claim that a nullability check compiler feature should not have anything to do with multi-threading. I provided various examples with code flows that show multi-threading and code flow issues that would generate compiler-warning-free code but runtime exceptions, and still they do not believe that my arguments are valid.

Can somebody from the Kotlin world please make some statements on what was the rationale behind the decision of considering mutable properties always as unsafe on multiple accesses? Am I missing some big difference and both sides are right or is it simply a more philosophical question where the .net devs have a different POV than the Kotlin devs?

I would really appreciate if some Kotlin experts can shed some light on this topic :slight_smile:

Kind Regards
Daniel

This is not about thread safety. The field could be set to null by the current thread during any random method call or whatever.

if(field != null) {
    doSomething() // could set field to null
    field.foo() // nobody can guarantee here that field is != null
}

The error wouldn’t be there if the problem was only thread safety. When you do multi-threading-stuff, you’re generally on your own with making sure it’s safe. In this case, the problem is more, that once something is smart-cast to non-null the compiler can’t easily un-smart-cast it once you call any random method.

4 Likes

Hi @danielku15

I think that this is the point.

Just wanted to add some of the runnable (and editable) examples for fun (taken from this post: Nullable property lazy allocation).

fun main(args: Array<String>) {
//sampleStart
    foo = true

    if (foo != null) {
        println("foo: $foo")
    }
//sampleEnd
}

var foo: Boolean? = true 
    get() = field.also {field = null}
fun main(args: Array<String>) {
//sampleStart
    foo = true

    if (foo != null) {
        println("foo: $foo")
        println("foo: $foo")
        println("foo: $foo")
    }
//sampleEnd
}

var foo: Boolean? = true 
    get() = when(field) {
        true -> { field = false; true }
        false -> { field = null; false }
        null -> { field = true; null }
    }
fun main(args: Array<String>) {
//sampleStart
    if (foo != null) {
        doSomething()
        print("foo: $foo")
    }
//sampleEnd
}

var foo: Boolean? = true

fun doSomething() {
    // doSomething ...
    foo = null
}
1 Like