The inline modifier

One way in which we can control the performance of our higher-order functions is through the usage of the inline modifier keyword. The inline modifier allows us to improve the performance of our higher-order functions by indicating to the compiler that the implementation of a function can be inlined at the call site, thereby avoiding the performance overhead associated with variable capture and class instantiation of a higher-order function.

To explore how this works, let's start by defining this function, safelyRun:

fun safelyRun(action: () -> Unit) {
try {
action()
} catch (error: Throwable) {
println("Caught error: ${error.message}")
}
}

This is a top-level, higher-order function that will run whatever lambda is passed to it while wrapping that call in a try/catch block. While this is defined as taking a function parameter, at the compiler level, it requires an instance of the Function0 class:

public static final void safelyRun(@NotNull Function0 action) {
Intrinsics.checkParameterIsNotNull(action, "action");

try {
action.invoke();
} catch (Throwable var4) {
String var2 = "Caught error: " + var4.getMessage();
boolean var3 = false;
System.out.println(var2);
}

}

The Function0 class will capture any variables that are referenced within the lambda in order to provide access to those values within the lambda.

So, when we use our safelyRun() function like this:

fun main() {
val greeting = "Hello"
safelyRun {
println("$greeting Kotlin")
}
}

The compiler will generate code, shown as follows:

public static final void main() {
final String greeting = "Hello";
safelyRun((Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke() {
this.invoke();
return Unit.INSTANCE;
}

public final void invoke() {
String var1 = greeting + " Kotlin";
boolean var2 = false;
System.out.println(var1);
}
}));
}

Notice that when calling safelyRun(), an anonymous inner class is created that then has an implicit reference to the outer class and its fields. This is how the lambda is able to capture any required state, such as properties or local variables. This anonymous inner class will be instantiated each time a lambda is evaluated.

If a higher-order function is operating on a large collection, iterating over the collection, for example, the overhead associated with capturing the lambda state will be paid for each iteration of the loop, requiring greater and greater amounts of memory and an increased number of virtual method calls.

Thankfully, we can avoid this overhead by leveraging the inline keyword. By adding an inline function to our higher-order function, we instruct the compiler to replace invocations of a given function with the actual implementation of that function at the call site. Let's illustrate this by updating our example.

First, we add the inline keyword to our safelyRun() function, as follows:

inline fun safelyRun(action: () -> Unit) {
try {
action()
} catch (error: Throwable) {
println("Caught error: ${error.message}")
}
}

Now, if we look at the code generated, we'll see the impacts of adding inline:

public static final void main() {
String greeting = "Hello";
boolean var1 = false;

try {
int var7 = false;
String var8 = greeting + " Kotlin";
boolean var4 = false;
System.out.println(var8);
} catch (Throwable var6) {
String var2 = "Caught error: " + var6.getMessage();
boolean var3 = false;
System.out.println(var2);
}

}

The body of safelyRun() has been rewritten into the body of main(). This avoids the need to create a new instance of Function0 to capture the greeting variable defined within main(). By avoiding the need to create a Function0 instance on each invocation of safelyRun(), we've improved the performance of our code.