`

Learn Kotlin Inline Functions in 3 Minutes

Kotlin allows functions to be passed and returned from other functions. This feature is called “first class” support for functions and brings in advantages of functional programming. To learn about inline, we will go over the hidden costs of functional programming, see how inline can help reduce those costs, then wrap up with places where it should not be used.

A higher-order function comes with run-time penalties. First, memory is allocated for lambdas as function parameters. They are compiled to either Function classes or anonymous classes, and both cost memory. Additionally, anonymous classes are slower than Function classes and used only when local variables are captured. Lastly, virtual calls add unnecessary cost.

The inline keyword was added to Kotlin to reduce the associated costs of higher-order functions. It modifies the Java bytecode by injecting the function and its parameters directly where the function is called, the call site. Using it is easy, just add inline before your function:

inline fun foo(...) = { ... }

To see how inline impacts bytecode, consider two functionally equivalent higher-order functions, filter and filterNotInline. The former is part of the Kotlin standard library and the latter is a copy of it with inline removed.

val list = listOf('a', 1) // list with a Char and an Int
// both print 'a'
println(list.filter( { it is Char} )) // inline call site
println(list.filterNotInline( { it is Char} )) // not-inline call site
// copy of library function with inline keyword removed
fun <T> Iterable<T>.filterNotInline(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

Decompiling this code will show how inlining works under-the-hood. Also, it was compiled to JVM bytecode and decompiled to Java for readability. Since filter is inlined, its code is copied directly. Here’s the call site at the first println statement.

// list setup occurs before this
while(var6.hasNext()) { // do filtering, inlined
   Object element$iv$iv = var6.next();
   if (element$iv$iv instanceof Character) {
      destination$iv$iv.add(element$iv$iv);
   }
}
List var12 = (List)destination$iv$iv;
System.out.println(var12); // print result of .filter

The less efficient version requires overhead via a function call to filterNotInline, which contains code similar to the above.

var12 = ((Inline.Companion)this).filterNotInline((Iterable)list, (Function1)null.INSTANCE);
System.out.println(var12); // print result of .filterNotInline

Inlining is usually great for performance, but there are good reasons why functions are not inlined by default:

  • Using an inlined function increases code length because the function is copied to every call site. This is especially true for long functions.

  • Inlining applies to all lambda parameters of the inlined function. So, an inlined function with three lambdas as parameters will copy them all. You can use noinline on individual parameters to prevent copying.

  • Regular return statements are not allowed. You must use a label if you wish to return from your inlined function.

  • Inline functions do not have access to data less visible than themselves.

In addition to functions, properties can be inlined too! This can be done at the individual getter and setter or the entire property.

inline var area: Area // inline property - cannot have backing field
    get() = ...
    set(value) { ... }

var area: Area // inline getter and/or setter
    get() = ...
    inline set(value) { ... }

By now you should have an understanding of the benefits and tradeoffs of inline. Feel free to post any questions you have in the comments!

You can also follow me on Twitter for more Android content.

Trevor NemanicComment