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!

Today: Inline functions

If you are a fan of writing generic Java code, you probably stumbled upon the fact generic types are erased at runtime[1]. One way to go around this is to use an extra parameter with the class literal as runtime token[2]. Imagine you want to write a generic converter to transform an integer to given type. In Java, you could write such a function as:

public class UtilityClass {
    static <T> T convert(int a, Class<T> type) {
        if (type == Integer.class) return type.cast(a);
        else if (type == Long.class) return type.cast((long) a);
        else if (type == String.class) return type.cast(Integer.toString(a));
        throw new RuntimeException("Cannot convert to " + type.getName() + " yet...");
    }
}

// Call it
UtilityClass.<Integer>convert(29, Integer.class);

// Or ditch the type arguments, as it can be inferred
UtilityClass.convert(23, Long.class);

Since Kotlin is compiled to the very same JVM bytecode, Kotlin’s language designers couldn’t possibly do this any better, right? Well… As a matter of fact… They did!

Let’s look at inline functions, and how above code could be improved. Perhaps when you heard someone talking about inline functions, you thought they meant nesting one function into another[3]. That’s not the case at all. Inline functions are functions that do not exist at the JVM level. Uhm what? Yeah, consider following code:

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

You can call this like any other function:

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

When we decompile it, watch and learn:

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

Ah, now you get it! Inline literally means inline, the code within the function is "taken" and "put" where the function is called. Interestingly, the name of the function is stored as a variable, even if it is not used at all. Why does such language feature even exist? Would it be, just for some tiny performance optimization?

No, there is a more useful reason. If you look back at the convert function above, from the calling side you know the generic type exactly (first an Integer and then a Long). But from within the function, we had to figure out type <T>, so we needed the literal class as a runtime token. On the other hand, if the convert does not exist at all, you don’t have to work around the type erasure problem. But then, we still like to write our code in a generic way…

Inline functions are a way to do both! Since the generic type is known from the caller’s point of view, all you have to do is tell the compiler it 'knows' the generic type by using the keyword reified.

inline fun<reified T> convert(a: Int) =
    when (T::class) {
        Int::class -> a as T
        Long::class -> a.toLong() as T
        String::class -> a.toString() as T
        else -> throw RuntimeException("Cannot convert to ${T::class} yet")
    }

Now, let’s call this function

fun main() {
    convert<Int>(29)
}

and consider the simplified decompiled Java code:

public static final void main() {
    int a$iv = 29;
    boolean $i$f$convert = false;
    KClass var2 = Reflection.getOrCreateKotlinClass(Integer.class);

    if (Intrinsics.areEqual(var2, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
        Integer.valueOf(a$iv);
    } else if (Intrinsics.areEqual(var2, Reflection.getOrCreateKotlinClass(Long.TYPE))) {
        Long.valueOf(a$iv);
    } else if (Intrinsics.areEqual(var2, Reflection.getOrCreateKotlinClass(String.TYPE))) {
        String.valueOf(a$iv);
    }

    throw new RuntimeException("Cannot convert to " + Reflection.getOrCreateKotlinClass(Integer.class) + " yet");
}

Aha, that’s how it’s done!

You might wonder why Kotlin’s compiler does not do even more. Above’s code clearly has one real code path, so it could just remove all unused if statements altogether. You would be right, the Kotlin development team could invest in optimizing inline functions even more. But actually they don’t have to, because once the JVM is hot, the JVM itself will eliminate unused code paths.

There are some caveats when you use an inline function, but that’s a topic for another day. Stay tuned for more!


1. According to this Stack Overflow question, at least half a million people!
2. See also Class Literals as Runtime-Type Tokens from the Java™ Tutorials.
3. We already talked about this, see the nested functions.
shadow-left