Why does Kotlin create null checks on null-safe parameters but not on null-safe returns?


#1

New to Kotlin, and I was wondering when I have a kotlin function, why does the generated byte code check that the parameter is non-null, but not that the return value is non-null?

For instance the Kotlin code:

fun takesNonNullValue(shouldBeNonNull: String): String {
    return shouldBeNonNull
}

Result in the decompiled Java:

   public static final String takesNonNullValue(@NotNull String shouldBeNonNull) {
      Intrinsics.checkParameterIsNotNull(shouldBeNonNull, "shouldBeNonNull");
      return shouldBeNonNull; // <- Why no Intrinsic check?
   }

I want to learn more about this language decision, because the return check would help to more gracefully handle an issue that came up in some kotlin code I wrote using java-interop (The example is a simplified version of some Android Room code I wrote with a function wrapping a kotlin interface call):

fun main(args : Array<String>) {
     print(myCode()) //<- prints the value 'null', but would have been nice if an IllegalStateException was thrown since myCode returns a String and not a String?
}

fun myCode(): String {
    val interfaceInstance: KotlinInterface = JavaToBeCalledByKotlin();
    val shouldBeNonNull : String = interfaceInstance.nonNullStringPlease() // <- Actually returns Null
    return shouldBeNonNull 
}

interface KotlinInterface {
   fun nonNullStringPlease(): String
}
public class JavaToBeCalledByKotlin implements KotlinInterface {
    @Override
    public String nonNullStringPlease()  {
        return null;
    }
}

Originally my mentality was “kotlin should trust kotlin so it makes sense that no null assertion is done when myCode() returns a null”. However that logic broke down with my first example where kotlin doesn’t trust kotlin code, and validates parameters are not null even though they are declared to be. So I’m curious why parameters are not trusted but returns are trusted?


#2

I would say that returns are trusted because the compiler ensures that the function returns the correct type.
However, that is not the case for parameters because in Java you could pass a null value to a Kotlin function that only accepts non-null values, and the Java compiler wouldn’t complain.
To prevent this the Kotlin compiler inserts null checks for each non-null parameter, allowing the function to be called from any language while enforcing the nullability rules.


#3

Hmm I’m not quite bought in by your reasoning, since the compiler can enforce that the parameters are nonNull in a pure Kotlin code base.

In a Kotlin and Java code base, if we go with your reasoning that Kotlin has decided that it should protect itself when interpolated into Java. Why doesn’t Kotlin provide that same level of protection for when Java is interpolated into Kotlin? My example shows an instance where the compiler shouldn’t be trusted because the interface can not be guaranteed to have a safe implementation.

I’m hoping there is another explanation on why this implementation is done outside of Java-Kotlin interoperability.


#4

Good question. Consider editing it to format the code. On a separate line before your code type:
```kotlin

On a separate line after type
```

The formatting of the lines of code in-between should happen automatically.


#5

Thanks for the suggestion! Will do :slight_smile:


#6

When you call Java, you’re going outside the sandbox. Kotlin can’t control what happens in Java code. You have to check what you get back in Kotlin before passing it along.


#7

Aaah! Much better!


#8

That helps with the trust on returns. But is that the only reason there is no trust on the parameters because of java interpolating kotlin? It seems strange to me to force kotlin concepts on Java code… shouldn’t that stay in the sandbox of kotlin as well?


#9

I think you nailed itTombaMarina: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#null-safety

I’m not really satisfied with the answer because I think it’s asymmetrical to force kotlin concepts into java but not platform-types into kotlin, but Kotlin has documented their stance and why they choose to do this, so this question is answered.


#10

Hmm… This is a subtly different question than what I originally thought. You’re asking, “Why doesn’t Kotlin null-check the returns from any Java functions it calls?”

Answer: Unless the Java is compiled with the @NotNull annotations (or Kotlin’s developers do some special magic for common Java interfaces and classes) Kotlin can’t tell whether Java methods are null safe. As I look at your question some more, I can see that you fooled Kotlin!

I still think that the solution is not to check every function return in Kotlin code (there is a performance cost for doing so). I think that leaving it up to the programmer to test the return values of any Java code they call is a reasonable compromise.

If it’s your own Java code that you’re calling from Kotlin, try adding this to your build if you haven’t already:

<dependency>
    <groupId>org.jetbrains</groupId>
    <artifactId>annotations</artifactId>
    <version>17.0.0</version>
</dependency>

I’m surprised IntelliJ isn’t highlighting that line of Java code where it implements the KotlinInterface. Maybe you need to dial up the severity of that warning.

In any case, annotate nonNullStringPlease() with @NotNull so you get a compile time error.

In your Java project, you can right-click your src/main/java package and choose “Analyze” then “Run Inspection by Name”, then type @NotNull for the inspection. It should highlight the missing @NotNull in your example. Fix those errors and you should be golden.

P.S. First time I ran that inspection I got 560 errors. :slight_smile: That took most of a day…


#11

There is along thread about this already. And yes, it is indeed inconsistent. The current state allows Kotlin code to break even in situations where the Kotlin code does not see that java code is used. For example, the Kotlin code could be using a Kotlin interface, and the runtime implementation of this interface could be a Java class.


#12

In this case the check is present

fun env(name: String): String = System.getenv(name)

#13

Do you have a link to the thread handy?