You never touched Groovy, nor did you jump on the Scala train. Clojure never attracted you; and you heard about Ceylon long after the language had already died. You are one of those old-fashioned Java folks! But now, after all those years, you want to join the cool Kotlin kids. So, where to start? Let’s discover the language together by decompiling it to Java code. Today: Inline functions caveats!

Today: Inline functions caveats!

Last time we discovered inline functions. As I mentioned in said blog post, there might be some things you may run into. The power of the inline keyword is that it encompasses the entire function, including its parameters. When you use a function as parameter

inline fun add(a: Int, b: () -> Int) = a + b()

fun main() {
    add(2) { 2 }
}

its inlined as well:

public static final void main() {
    int a$iv = 2;
    boolean $i$f$add = false;
    int var2 = false;
    int var4 = 2;
    int var10000 = a$iv + var4;
}

This could be exactly like you intended. But chances are you want the b function executed lazily, just like a non-inline function with a function as a parameter. Luckily Kotlin provides a way to do this as well, with the noinline keyword:

inline fun add(a: Int, noinline b: () -> Int) = a + b()

fun main() {
    add(2) { 2 }
}

By just one little keyword, the bytecode changes, and so does the decompiled Java code:

public static final void main() {
    int a$iv = 2;
    Function0 b$iv = (Function0)null.INSTANCE;
    boolean $i$f$addNI = false;
    int var10000 = a$iv + ((Number)b$iv.invoke()).intValue();
}

Nice, the add function is still inlined, but the b function not any longer!

Just when you think all your problems are gone, another thing pops up. You want to inline a function parameter (so no extra keyword). But once you try to call from that function a non-inline function, which has a function as a parameter, the compiler starts complaining:

fun bar(f: () -> Unit) = f()
inline fun foo(f: () -> Unit) = bar { f() } // compile error: Can't inline 'f' here: it may contain non-local returns.

The compiler does not allow this, because normally return statements in a lambda are not allowed at all. But for inline functions it does, since inline functions don’t really exist, the return statement applies to the "outer" context:

fun qux(returnFromInlinedFunction: Boolean) : Int {
    someInlineFunction {
        if (returnFromInlinedFunction) return 4 // `qux` returns 4
    }

    return 2 // `qux` returns 2
}
Such returns (located in a lambda, but exiting the enclosing function) are called non-local returns.
— Kotlin docs

Now, let’s look back at foo function. The lambda f is inlined, so it also has the 'extra' option to use non-local returns. This time that could lead to some bizarre problems, because the bar function itself is not inline. The compiler cannot know if you want to return from bar or for foo itself. Therefore, Kotlin’s developers chose not to allow such code.

Most of the time you don’t want to use a return statement in your lambda’s. To tell the compiler you want to 'remove' the non-local return feature, you can use the crossinline keyword:

fun bar(f: () -> Unit) = f()
inline fun foo(crossinline f: () -> Unit) = bar { f() } // works!

Well, that’s enough for one day. Stay tuned for more!

shadow-left