The keyword reference page of the Kotlin official documentation
I invite you to spend some time going through the list.
Do you know all the keywords?
If you wrote some Kotlin code, you probably encountered most of them. Some keywords in that list though are less commonly used, therefore they could be harder to remember. One example: the noinline
and the crossinline
keywords.
The keyword reference gives the following definitions:
noinline
turns off inlining of a lambda passed to an inline function
crossinline
forbids non-local returns in a lambda passed to an inline function
The latter can sound a bit complicated for an inexperienced developer. Given that those keywords are not frequently used in daily development, remembering the exact semantic could be harder. If you speak with other Kotlin developers, chances are that they never used the crossinline
keyword, and they probably don't know when to use it.
The official documentation can help understand those keywords, but in this case, I believe an example is worth a thousand words.
In this blog-post, we will learn the semantic of the noinline
and crossinline
keyword, with a simple example using the Kotlin Playground(with snippets that you can run and edit in your browser to follow along the article).
inline
To explain those keywords, we have to first explain the inline
keyword, and the Inline Functions feature of Kotlin.
Let’s start with an example: a function to do something if you’re running on Debug:
fun doOnDebug(block: () -> Unit) { // On Android you can replace with a BuildConfig.DEBUG if (Env.DEBUG) { block() } } fun main() { doOnDebug { println("I'm on debug ¯\\_(ツ)_/¯") } } object Env { val DEBUG = true }
The function is trivial: it takes a lambda as a parameter and executes it if you execute on a Debug environment (we are simulating it using the Boolean DEBUG
property).
Using the Show Kotlin Bytecode feature of IntelliJ, we can see how the equivalent bytecode code will look like¹:
Decompiled bytecode of a regular function with lambda parameters¹
As we can see from the image, the doOnDebug
function is compiled to a function that takes a Function0
as a single parameter. Function0
is an interface declared in the Kotlin Standard Library for the JVM. It's a functional interface, used to pass a function that takes zero parameters from Java to Kotlin.
Kotlin offers the Inline Functions feature exactly to mitigate this overhead. If we add the inline
keyword in front of the function definition:
inline fun doOnDebug(block: () -> Unit)
The resulted bytecode will look like this:
Decompiled bytecode of an inline function invocation
As we can see from the bytecode, the doOnDebug
code is inlined in the main()
function body. The lambda parameter is affected as well as its body is also copied.
In other words, inline will “copy-paste” the function body, and the lambda parameter at every call site.
This means that we can avoid creating an instance of a Function0
, resulting in saved memory allocation.
To recap, the inline
keywords allows you to reduce the runtime overhead of functions taking lambda as parameters by:
- Inlining their body at every call site.
- Inlining every lambda parameter at the call site.
Please note that inlining functions can increase the size of your generated code, so make sure you do it only for small functions.
The full example is available in this playground (you can edit the code and run it as well):
With noinline
and crossinline
you can then customize the inline
behavior, let's see how.