Idiomatic Kotlin: Inline functions

Tompee Balauag
Familiar Android
Published in
6 min readJul 20, 2018
Photo by Olesya Grichina on Unsplash

This article is a part of the Idiomatic Kotlin series. The complete list is at the bottom of the article.

In this article, we will try to explore how inline function works. We will try to investigate what happens under the hood by using different examples.

What is an Inline Function?

Inlining is an attempt to remove (or reduce) the runtime overhead of lambda functions by moving (or inlining) the higher order function and its lambda argument body to the call-site. By using this approach, no anonymous classes and function reference objects will be created during runtime. An inline function therefore is a function whose body and it’s lambda argument’s body will be inlined to the call-site at compile time. Inlining has its own set of rules and restrictions due to the nature of its approach, and we will discuss them later.

Motivation

The main motivation for inlining is reducing (or eliminating) the runtime overhead induced by using lambda expressions and function references.

How to define an inline function?

Inlining a function is as simple as adding the inline modifier in the function declaration. Every function in theory can be converted to an inline function but it works best for higher order functions that accepts lambdas. Inlining a “normal” function will net you this warning (when using Intellij-based IDE)

Warning:(123, 4) Kotlin: Expected performance impact of inlining '...' can be insignificant. Inlining works best for functions with lambda parameters

How does it work?

Warning: This section involves a lot of code and examples for us to visually recognize what is happening under the hood.

To understand how inlining works, let’s first look at a normal function without inlining.

The nonInlinedFilter is our static function approach to filtering a list of integers. The decompiled version of this would be

If you have been following the Idiomatic Kotlin series, this result should be familiar to you. The lambda parameter of the notInlinedFilter is converted to an instance of the Function1 interface. This means a class and an instance is generated just to pass lambda.

Now let’s look at what happens if we convert our function to an inline function

The decompiled version would be

Note that the inlinedFilter function remains the same. This means that an inline function in Kotlin behaves the same way as a regular function in Java. You can still call it in Java by satisfying its parameters. Notice another thing? Yup, the inlinedFilter body is copied into the calling function as a replacement to the function invocation. This is what inlining is all about. Since the code is directly substituted in the call-site, there is no need to instantiate an anonymous function reference object. Let us summarize what we know so far regarding inlining based on our examples so far.

  1. The inlined function body is inlined to the call-site.
  2. The lambda parameter body is inlined to the call-site therefore, there is no need to instantiate function references.
  3. Since code is duplicated, under the hood, code can become larger.
  4. The inlined function behaves the same way as a regular function in Java.

Now let us look at another example, this time passing a function reference instead of a lambda literal.

The decompiled version would be

Lets see if all our conclusions from last example still holds true.

  1. The inlined function body is inlined to the call-site. ✔
  2. The lambda parameter body is inlined to the call-site therefore, there is no need to instantiate function references. ✔
  3. Since code is duplicated, under the hood, code can become larger. ✔
  4. The inlined function behaves the same way as a regular function in Java. ✔

So far so good. The function reference is directly invoked instead of passing a function reference instance.

Now let’s look at another example

This time, the lambda body is not defined in the call-site, but is a passed from a function. Let’s look at the decompiled code.

All of our conclusion holds true again but we need to add another item. Notice that the lambda parameter body is no longer inlined to the call-site. This is because prior to calling the inlined function, the lambda is already instantiated as a function type. The compiler does not have enough information about what the lambda body looks like anymore and just resolved on invoking it instead. Therefore, we need to update our conclusions.

  1. The inlined function body is inlined to the call-site.
  2. The lambda parameter body, when defined in the call-site, is inlined to the call-site therefore, there is no need to instantiate function references.
  3. Since code is duplicated, under the hood, code can become larger.
  4. The inlined function behaves the same way as a regular function in Java.
  5. When the body of a function type is not declared in the call-site, it will not be inlined.

How about inlining the lambdaInstance function you say? Let’s try and look at the decompiled code.

Works as expected. The inlinedFilter code is inlined in lambdaInstance and the latter inlined to the lambdaInstanceTest call-site. We now have another item in our conclusion.

  1. The inlined function body is inlined to the call-site.
  2. The lambda parameter body, when defined in the call-site, is inlined to the call-site therefore, there is no need to instantiate function references.
  3. Since code is duplicated, under the hood, code can become larger.
  4. The inlined function behaves the same way as a regular function in Java.
  5. When the body of a function type is not declared in the call-site, it will not be inlined.
  6. The inlined parameter can be passed to another inline function.

Now let’s take a closer look into the inline parameters. We know that we can invoke them inside the inline function so let’s add that to the list.

7. Inlined parameters can be invoked inside the inline function.

How about storing them to a variable for future use? Let’s try this code and see what happens

Compiler gives this an error

Error:(12, 34) Kotlin: Illegal usage of inline-parameter 'predicate' in 'public inline fun inlinedFilter(list: List<Int>, predicate: (Int) -> Boolean): List<Int> defined in functiontypes in file Inline.kt'. Add 'noinline' modifier to the parameter declaration

This happens because inlining can only be performed on lambda bodies that will be synchronously invoked. Saving an instance of the lambda will require an object and defeats the purpose of inlining. Let’s add this conclusion and summarize all our findings.

  1. The inlined function body is inlined to the call-site.
  2. The lambda parameter body, when defined in the call-site, is inlined to the call-site therefore, there is no need to instantiate function references.
  3. Since code is duplicated, under the hood, code can become larger.
  4. The inlined function behaves the same way as a regular function in Java.
  5. When the body of a function type is not declared in the call-site, it will not be inlined.
  6. The inlined parameter can be passed to another inline function.
  7. Inlined parameters can be invoked inside the inline function.
  8. Inlined parameters cannot be instantiated into a function type reference.

By now, you should already be familiar with the benefits of inlining. On the next article, we will talk about the considerations when using inline functions.

Check out the other articles in the idiomatic kotlin series. The sample source code for each article can be found here in Github.

  1. Extension Functions
  2. Sealed Classes
  3. Infix Functions
  4. Class Delegation
  5. Local functions
  6. Object and Singleton
  7. Sequences
  8. Lambdas and SAM constructors
  9. Lambdas with Receiver and DSL
  10. Elvis operator
  11. Property Delegates and Lazy
  12. Higher-order functions and Function Types
  13. Inline functions
  14. Lambdas and Control Flows
  15. Reified Parameters
  16. Noinline and Crossinline
  17. Variance
  18. Annotations and Reflection
  19. Annotation Processor and Code Generation

--

--