Scope functions behavior in property setter

I tried this code on Kotlin playground (Kotlin version 1.4.30):

class A() {
    var i: Int? = null
    	set(v) {
		    v ?: run {
				field = 7
		        return
            }
            field = v
        }
}

fun main() {
    val i: Int? = null
    i ?: run {
        val a = A()
        a.i = null
        print("a.i: ${a.i}; ")
    }
    println("i: $i")
}

and the output was as expected, a.i: 7; i: null.

If I replaced run in the statement i ?: run { to let, also, or apply, I got an Unresolved reference error, also as expected.

However, if I replaced the run in the statement v ?: run { with any of let, also, or apply, I didn’t get an error message. Instead I got the original output, a.i: 7; i: null in all cases.

Is this the expected behavior? Are the scope functions supposed to behave differently in a property setter? If so, is this a documented behavior? Thanks.

There’s two run functions. One is an extension method like let, also, and apply. The other is not an extension methods. In the property setter, you can call extension methods because this is available as the receiver. In main, there is no this so you cannot call extension methods without explicitly providing one (a.let {...}). But the non-extension run works fine.

While they work similarly, your two calls to run are actually calling different implementations. The name is just overloaded. You can see both listed in the documentation

3 Likes

Thanks for the answer. I missed the implied this. in the primary constructor.

A follow up question:if instead of field = v in the setter , I have field = if (v < 0) 0 else v, like so:

class A() {
    var i: Int? = null
    	set(v) {
		    v ?: run {
				field = 7
		        return
            }
            field = if (v < 0) 0 else v
        }
}

the compiler doesn’t complain if I use run or let, but with apply and also it complains of:

Operator call corresponds to a dot-qualified call ‘v.compareTo(0)’ which is not allowed on a nullable receiver ‘v’.

Somehow let and run result in v being smart cast but not apply and also? Is it because the compiler can choose the unwrapped Int over Unit in the former case but couldn’t decide between the unwrapped Int and A, which is the type of this returned by apply and also?

Thanks.

Ok, the compiler gives the same error message with let and run if I replace return in the setter with v or A(), like so:

		    v ?: run {
				field = 7
		        A() // or v
            }

It looks like it can smart cast v after the elvis statement only if there’s no alternative type.

1 Like