A Very Peculiar, but Possibly Cunning Kotlin Language Feature

This has caught me by surprise. After studying the Kotlin language to learn about how to best leverage this interesting new language for jOOQ, I stumbled upon this puzzler. What do you think the following program will print?
fun main(args: Array) {
    (1..5).forEach {
        if (it == 3)
            return
        print(it)
    }

    print("done")
}
Well… You might have guessed wrong. The above will print:
12
It will NOT print what most people might expect:
1245done
Note to those of you who are not surprised: The above is peculiar for someone used to working with Java 8, where the following code will indeed print 1245done:

public static void main(String[] args) {
    IntStream.rangeClosed(1, 5).forEach(it -> {
        if (it == 3)
            return;

        System.out.print(it);
    });

    System.out.print("done");
}

The syntactical reason is explained in this section of the Kotlin manual: https://kotlinlang.org/docs/reference/returns.html In lambdas / closures, the return statement will not (necessarily) return from the lambda / closure, but from the immediate enclosing scope of the lambda / closure. The rationale has been kindly given to me by Dmitry Jemerov from JetBrains in two tweets:
Cunningly, the Kotlin language has removed language-based support for Java constructs like try-with-resources, or the synchronized statement. That’s very reasonable, because these language constructs don’t necessarily belong in the language (as we’ve previously claimed in another blog post), but could be moved to libraries instead. For example:
// try-with-resources is emulated using an
// extension function "use"
OutputStreamWriter(r.getOutputStream()).use {
    it.write('a')
}
(criticism here) Or:
// Synchronized is a function!
val x = synchronized (lock, { computation() })
See also: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/synchronized.html After all, even in Java, the language feature only works because the language depends on library types, like Iterable (foreach), AutoCloseable (try-with-resources), or JVM features (monitor on each reference for synchronized)

So, what’s the deal with return?

Along the lines of the above rationale, when language designers want to avoid language constructs for things that can be implemented with libraries, but still want you to feel like these were language constructs, then the only reasonable meaning of return inside of such a “construct-ish” lambda / closure is to return from the outer scope. So, when you write something like:
fun main(args : Array) {
    val lock = Object()
    val x = synchronized(lock, {
        if (1 == 1)
            return

        "1"
    })

    print(x)
}
The real intention is for this to be the equivalent of the following Java code:

public static void main(String[] args) {
    Object lock = new Object();
    String x;

    synchronized (lock) {
        if (1 == 1)
            return;

        x = "1";
    }

    System.out.println(x);
}

In the Java case, obviously, the return statement exits the main() method, because there is no other reasonable stack frame to return from. Unlike in Kotlin, where one might argue the lambda / closure would produce its own stack frame. But it really doesn’t. The reason for this is the inline modifier on the synchronized function:
public inline fun <R> synchronized(lock: Any, block: () -> R): R {
    monitorEnter(lock)
    try {
        return block()
    }
    finally {
        monitorExit(lock)
    }
}
See also: https://kotlinlang.org/docs/reference/inline-functions.html Which means that the block closure passed as an argument isn’t really a pure lambda expression, but just syntactic sugar embedded in the call-site’s scope. Weird. Cunning. Clever. But a bit unexpected. Is this a good idea? Or will the language designers regret this, later on? Are all lambdas / closures potentially “language construct-ish”, where such a return statement is expected to leave the outer scope? Or are there clear cases where this inline behaviour just makes total sense? We’ll see. In any case, it is very interesting for a language to have chosen this path.

Liked this article?

Stay tuned for a follow-up about the exciting Kotlin language. In the meantime, read

10 thoughts on “A Very Peculiar, but Possibly Cunning Kotlin Language Feature

  1. So, Kotlin distinguishes between “lambda expressions” and “anonymous functions”. Not a choice that I’d have gone with…. I like what they did, but I wouldn’t have called them “lambda expressions”. I’d have called them “blocks” like they are in Perl.

    1. I suspect there is a grey area between pure functions and just scopes, indeed. I personally never really understood what the formal definition of a closure might be. It seems that different people have different ideas about them.

      1. @lukaseder: A closure if a function that has access to it’s lexical parent’s environment record even after the lexical parent has finished executing.

        An environment record is all of the variables in scope at a specific point.

        1. Yes, that’s how I understand it. But I’ve never seen a “closure” be able to jump anyplace within its lexical parent’s environment.

  2. It is also important to note that return@forEach will produce the expected behavior. You can always add the specific label you want to return to, which is something I quite like. What gets confusing is where it returns to by default. Another thing to note is, if you are using forEach, I think the expected and functional thing to do is add .filterNot { it == 3 } to get rid of the 3 before the forEach.

    1. Yes, interesting feature, to “return to labels”. Well, it’s kind of like continue

      if you are using forEach, I think the expected and functional thing

      I think that forEach is the non-functional thing to get rid of, to start with :)

    2. I did expected “1245done”. Now, three days later of coding Kotlin I find this article again and I only expected “12”. I couldn’t make my mind to expect “1245done” anymore!! I only understood it again after reading again. I think we will get used to it, and actually with the label features I think this style gives you great power.

      1. Great feedback, thanks – that’s good to know. So, apparently, the feature is surprising for a Java dev, but very intuitive, so it does add value.

  3. This shouldn’t be surprising if you consider the language’s influences. Kotlin is very much Scala redesigned for Java developers. As such, it inherits ideas like Scala’s non-local return behavior. Now that Java has lambda functions this seems weird, but only because they chose the opposite design. Gafter’s lambda proposal had non-local returns too, though they had to be named and were not the default. The problem, of course, is that without sufficient intelligence the implementation is to throw an exception. That is one reason why it is not idiomatic in Scala.

    1. Kotlin is very much Scala redesigned for Java developers

      OK, but with a stress on Scala or on Java? :) I would have expected Kotlin to be closer to Java here. After all, with the various labelling techniques available, the default could have been matching Java, wheras Scala-style returns are still possible.

      Anyway, it’s just a default for something that is probably not everyday usage. I don’t think this is really a good nor a bad decision, just a matter of taste.

Leave a Reply