Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin's logical 'and' doesn't short-circuit?

Tags:

kotlin

I was following along Kotlin's documentation at http://kotlinlang.org/docs/reference/null-safety.html#checking-for-null-in-conditions and tried adapting this example,

val b = "Kotlin"
if (b != null && b.length > 0) {
    print("String of length ${b.length}")
} else {
    print("Empty string")
}

to the case where b = null. In an IntelliJ Idea Kotlin project I have an app.kt with a main() function defined as:

fun main() {
    val b = null
    if (b != null && b.length > 0) {
        print("String of length ${b.length}")
    } else {
        print("Empty string")
    }
}

However, when I run this, I get two compilation errors:

Information:Kotlin: kotlinc-jvm 1.3.20 (JRE 11+28)
Information:2019-02-02 15:07 - Compilation completed with 2 errors and 0 warnings in 1 s 921 ms
/Users/kurtpeek/IdeaProjects/HelloWorld/src/app.kt
Error:(3, 24) Kotlin: Unresolved reference: length
Error:(4, 37) Kotlin: Unresolved reference: length

I understand that the compiler is evaluating b.length even though the first condition, b != null, is false. This surprises me because I thought that the first check was to 'short-circuit' the Boolean expression if needed and make the call to b.length 'safe'.

For example, in Python, you can do this:

In [1]: "foo" == "bar" and what.the.heck
Out[1]: False

which works even though what is not defined, because the and 'stops' since "foo" is not equal to "bar".

Is this indeed how Kotlin works? It seems like missing Python's 'short-circuiting' feature would be a limitation.

like image 625
Kurt Peek Avatar asked Feb 02 '19 23:02

Kurt Peek


1 Answers

Kotlin's && operator will short circuit (just like Java's) but only at runtime. What you are experiencing is a compile time error. The big difference to remember especially when comparing Kotlin (or Java) to Python is that Kotlin and Java are statically typed and have a compilation phase. So you'll get a compilation error if the types don't match up.

Let's go through these one at a time...

val b = "Kotlin"
if (b != null && b.length > 0) {
    ...
}

In this case, Kotlin will correctly infer that b is the type String, because you clearly set it to a String ("Kotlin"). We should note here that the String type cannot ever contain null. Knowing that, the b != null part of your if statement is unnecessary. However, after evaluating that (to true, always) it will evaluate b.length because b is a String and therefore has a length property. This example should compile fine (I didn't test it).

And next...

val b = null
if (b != null && b.length > 0) {
    ...
}

This code will not compile, let's go over why...

This code looks really similar but has one huge difference. In this case because you just set b to null, Kotlin is going to infer that b is an Nothing?. It has no information as to what type you want b to be, and you've set it to null (and because it's a val, it will always be null). Because b is null, it makes b nullable.

So, given that, when we compile b != null, that will always fail, because b can't ever be something that isn't null. But wait! We're compiling now... and when we run into b.length Kotlin will throw a compilation error because Nothing? does not have a length property!

Essentially, by setting b to null and not providing a type hint, Kotlin takes the only path it can to infer the type - Nothing?.

like image 135
Todd Avatar answered Oct 24 '22 14:10

Todd