Why does an unsafe .run () call work fine with a zero value in Kotlin?

I have the following code snippet:

val foo: String? = null foo.run { println("foo") } 

I have a null variable foo , which is actually set to null , followed by an insecure .run() call.

When I run the code snippet, I print foo even though the run method is called on null . Why is this? Why is there no NullPointerException ? Why does the compiler allow an unsafe call by an optional value?

If I pass println(foo) , I get a nice juicy null in the console, so I find it safe to assume that foo is actually null .

+5
source share
3 answers

I believe that there are two things that may come as a surprise: the semantics of the language that allows such a call, and what happens at runtime when this code is executed.

On the language side, Kotlin allows a null receiver, but only for extensions . To write an extension function that accepts a receiver with a null value, you must either explicitly enter a type with a null value, or use a valid upper bound for the type parameter (in fact, when you do not specify an upper bound, the default value is nullable Any? ):

 fun List<*>?.isEmptyOrNull() = this == null || size == 0 // explicit nullable type 

 fun <T : CharSequence?> T.nullWhenEmpty() = if ("$this" == "") null else this // nullable T 

 fun <T> T.identity() = this // default upper bound Any? is nullable 

This function is used in kotlin-stdlib in several places: see CharSequence?.isNullOrEmpty() , CharSequence?.isNullOrBlank() ?.orEmpty() for containers and String?.orEmpty() and even Any?.toString() . Some functions, such as T.let , T.run , which you asked for, and some others simply do not provide an upper bound for the type parameter, and the default value is nullable Any? . And T.use provides a nullable upper bound Closeable? .

Under the hood, that is, in terms of runtime, extension calls are not compiled into invocation commands of JVM members INVOKEVIRTUAL , INVOKEINTERFACE or INVOKESPECIAL (the JVM checks the first argument of such calls, implicit this , for being null and throwing NPE if so , and so called Java and Kotlin member functions). Instead, the Kotlin extension functions are reduced to static methods, and the receiver is simply passed as the first argument. Such a method is called using the INVOKESTATIC , which does not check the arguments for a null value.

Note that when the extension recipient can be null, Kotlin does not allow it to be used where a non-null value is required, without first checking for null:

 fun Int?.foo() = this + 1 // error, + is not defined for nullable Int? 
+8
source

To add to what @ holi-java said, there is nothing dangerous in your code at all. println("foo") quite fair whether foo null or not. If you tried something like

 foo.run { subString(1) } 

this will be unsafe and you will find that it will not even compile without any null check:

 foo.run { this?.subString(1) } // or foo?.run { subString(1) } 
+4
source

Is it because the top-level run function accepts all Any and Any? . therefore, the extension function with Null Receiver is not checked by Kotlin at runtime.

 // v--- accept anything public inline fun <T, R> T.run(block: T.() -> R): R = block() 

Indeed , the built-in run function is generated by Kotlin without any statement if receiver can be null, so it looks more like a noinline function generated in Java code, as shown below:

 public static Object run(Object receiver, Function1<Object, Object> block){ //v--- the parameters checking is taken away if the reciever can be nullable //Intrinsics.checkParameterIsNotNull(receiver, "receiver"); Intrinsics.checkParameterIsNotNull(block, "block"); // ^--- checking the `block` parameter since it can't be null } 

The IF you want to call in a safe way, you can use the safe-call operator ?. instead, for example:

 val foo: String? = null // v--- short-circuited if the foo is null foo?.run { println("foo") } 
+2
source

Source: https://habr.com/ru/post/1270507/


All Articles