Inline, noinline, crossinline, reified — Kotlin inline and the bytecode magic

Gabriel Brasileiro
ProAndroidDev
Published in
6 min readMay 7, 2024

--

https://kotlinlang.org/docs/reference/grammar.html

Kotlin is an amazing language, isn’t it? It harnesses the immense potential of Java but presents it in a simple and idiomatic manner, packed with various features. However, despite all its advantages, it does have a steep learning curve due to its numerous features.

Inline may seem complex to some, while to others, it’s like magic when seen through libraries like Koin that fully utilize its potential. But in reality, everything has an explanation, and it might be simpler than you think.

Inline

So, what is inline?

Inline is a special operator that allows us to transfer code from a High Order Function or properties of generic types to the point where we invoke the function at the Java bytecode level generated by Kotlin.

This brings us benefits in performance during code execution, as it will be transferred to the point of use.

Confused? Let’s go through a practical example.

High Order Function

High Order Functions (HOF) are highly renowned in Kotlin and other languages for handling the callback problematics. In everyday scenarios, we often encounter situations where the execution scope is transferred and return values from another Thread or Coroutine are eventually seen at the end of their operation.

But returning to the example, let’s look at the following code:

Notice that. The operation two returns to the execution scope of the main function, thus generating a simple conceptual callback. In Java code, we would have something along the lines of the following code:

Some operators generated by the bytecode will be left out to facilitate comprehension.

And now the magic happens. When we add the inline modifier at the beginning of the function with the HOF, we don’t have any visual changes other than the modifier:

However, if we look at the code generated by the bytecode, we see some changes. The block that was previously in the executeOperation function has been entirely transferred to the main function:

And furthermore, Kotlin allows you to add a return label to break the execution flow of the current scope:

Now Kotlin’s Scope Functions don’t seem so magical anymore, do they? Haha

Understanding the basics of inline functions, we can now move on to their special modifiers: noinline, crossinline, reified.

Noinline

Imagine now you’re working with more than one HOF in the same function, but you need each function to be executed independently.

This commonly occurs when we need to pass the HOF as a parameter within a function that is not inline or assign it to a variable within the block of the inline function.

See the next code:

At this point, Java will do approximately the same as the example of the HOF without inline. Therefore, we lose the power of return in the local block since we don’t know when the function will break the flow.

Crossinline

The crossinline is basically used to indicate that a HOF does not allow local return within the execution block.

It’s common to see this happen when your HOF is executed by another function that is not inline, functional interfaces, or SAMs (Single Abstract Methods) coming from Java code.

Both crossinline and noinline undergo considerable modifications by the generation script depending on the constructed flow, so the code generator may change the way the implementation happens in Java depending on the version. A new class or intermediate method may appear, but the principles remain the same.

Reified

From here on out, we’ll have even more “magic.” The power of Kotlin’s implicit types to the fullest!

One of the features I like the most and miss in similar languages is the use of Reflection.

Reified means “see something abstract as concrete.” In Java, especially, generic types are defined in very specific contexts and all of them, without exception, are abstract in their implementation.

An important point to note is that we don’t have this option directly in Java due to the way it is compiled and interpreted. So whenever we use functions that at some point reference the generic type in an object, we encounter a special property called Type Erasure when trying to retrieve this definition at runtime.

For those who are curious, I’ve left the official documentation discussing Type Erasure.

Knowing this peculiarity, let’s move on to the code.

Take a look at the generic type in the following code without the inline property:

Our String type encountered two distinct situations, both using reflection to both map its methods and filter them in a simple list.

You might sense various discomforts in the code: repetition in the declaration of the ::class.java block, validation of the instance using the reflection API, and the UNCHECKED_CAST, which shows that we don’t know the exact type in the cast due to Type Erasure.

So here comes the great insight: reified! When we apply the inline, we already know that the code is moved around by the bytecode, and because of this, we can also obtain the generic type in a concrete way since it will be directly referenced.

See how the code looks after we know its abstract type concretely in the method call:

In addition to being more concise, we managed to remove all the inconveniences mentioned… But wait, how did he do all that? What is the final result in Java?

To be brief, I’ll show part of the code for requireMethods and filterString and their transfers to the main block. Again, hiding part of the generation and improving the names of the generated variables to be more understandable:

And since everything is actually just “zeros and ones,” all the magic was in transferring the type to the location of use by invoking the methods of the Reflection API or simply transferring it for validation. Thus, in the end, we truly have something abstract treated as concrete, or as defined: reified.

The application of this feature goes far beyond when we use extensions with generic types and DelegatedProperties.

Restrictions

An important point to mention is that when publishing anything with inline in an API or library, your code should ideally be public throughout the function’s context. However, to prevent your code from being open and interfering with standards like the open/closed (OCP). Kotlin allows it to be internal as long as the special annotation PublishedAPI is applied to the method or class.

Example:

Inline Kotlin 2.0

The Kotlin in your second version implements some improvements. The inline modifier will be more powerful. If you never study about the callsInPlace it's a good moment to show you this common contract.

The callsInPlace method inside the DSL is responsible to tell the compiler about the instruction of a HOF. Some rules are applied at the moment of code compile!

For example when you need to assign a value in one var or val the compiler will verify the block you're trying to do that. In the case of HOFs you can have these problems because the software don't now about the context you are executing that. The method offer for you a way to force this recognize and permit assigns.

For more details read this article:

Starting in the version 2.0 with the K2 compiler callsInPlace be implicit in inline functions. Smart Casts now works in block of execution, and the bytecode is moved like the last kotlin version.

Note:

Track:

https://youtrack.jetbrains.com/issue/KT-7186/Smart-cast-for-captured-variables-inside-changing-closures-of-inline-functions

Conclusion

Inline functions have come to help, and understanding them is a fundamental step to optimize your daily work and studies.

Who would have thought that libraries like Koin would become so famous due to their simplicity in creating ideally complex scopes thanks to these features.

And now it’s your turn to apply all this magic to your APIs and workplace, creating functions and methods that are increasingly easy to use by the entire team!

Best regards, Gabriel Brasileiro.

--

--

Desenvolvedor Android, em busca de conhecimento. "Em nossas mãos os meios, e em nosso coração a vontade".