I have the following code fragment:
val foo: String? = null
foo.run { println("foo") }
I have here a nullable variable foo
that is actually set to null
followed by a nonsafe .run()
call.
When I run the code snippet, I get foo
printed out despite the fact that the run
method is called on a null
. Why is that? Why no NullPointerException
? Why does compiler allow a nonsafe call on an optional value?
If I pass println(foo)
, I get a nice juicy null
in the console so I think it's safe to assume that foo
is actually null
.
I believe, there are two things that both might be of some surprise: the language semantics that allow such a call, and what happens at runtime when this code executes.
From the language side, Kotlin allows nullable receiver, but only for extensions. To write an extension function that accepts a nullable receiver, one should either write the nullable type explicitly, or use a nullable upper bound for a type parameter (actually, when you specify no upper bound, the default one 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 feature 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 like T.let
, T.run
that you asked about and some others just don't provide an upper bound for the type parameter, and that defaults to nullable Any?
. And T.use
provides a nullable upper bound Closeable?
.
Under the hood, that is, from the runtime perspective, the extension calls are not compiled into the JVM member call instructions INVOKEVIRTUAL
, INVOKEINTERFACE
or INVOKESPECIAL
(the JVM checks the first argument of such calls, the implicit this
, for being null and throws an NPE if it is, and this is how Java & Kotlin member functions are called). Instead, the Kotlin extension functions are compiled down to static methods, and the receiver is just passed as the first argument. Such a method is called with the INVOKESTATIC
instruction that does not check the arguments for being null.
Note that when a receiver of an extension can be nullable, Kotlin does not allow you to use it where a not-null value is required without checking it for null first:
fun Int?.foo() = this + 1 // error, + is not defined for nullable Int?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With