Photo by Mickey O’neil on Unsplash
Inline functions were mainly designed to optimize performance, but they also offer a wide range of interesting utilities. Let’s take a closer look.
The inline modifier
Methods in Kotlin can be marked with the inline modifier at the beginning of the function declaration:
inline fun someInlinedFunction() {
try {
//statement 1
//...
//statement N
} catch (e : Exception) {
...
} finally {
...
}
}
Doing so, we make the compiler know that the function requires a special treatment: the generated bytecode for the method implementation will be inserted anywhere the function is called.
For example, with the following code:
fun main() {
println("Gonna call inlined function...")
someInlinedFunction()
println("Inlined function called...")
}
The resulting code would be something similar to:
fun main() {
println("Gonna call inlined function...")
try {
//statement 1
//...
//statement N
} catch (e : Exception) {
...
} finally {
...
}
println("Inlined function called...")
}
“Invocations to inline functions are replaced by the generated bytecode function implementation”
Calling a standard function is an expensive process in terms of resources: memory is allocated, state is saved, stack is updated, and so on. But when using an inline function, the whole process is simplified and, as a result, overall performance is optimized.
When to use inline functions
Inline functions come specially in handy with lambda parameters. Every lambda argument passed to a function is eventually compiled down to an instance of an anonymous class implementing the corresponding functional interface. For instance, a lambda for a click listener on an Android view…:
//XXX: lambda for click listener
val listener : (View) -> Unit = { it.sendAnalytics() }
val btn = ...
btn.setOnClickListener(listener)
…will generate the following code:
//XXX: functional interface impl for previous lambda
val listener = object : View.OnClickListener {
fun onClick(v : View) : Unit {
v.sendAnalytics()
}
}
val btn = ...
btn.setOnClickListener(listener)
So, if the function is not inlined, each lambda parameter makes the compiler define a new class and create a variable of that class.
As you can imagine, all these operations may have a negative impact on performance. So it’s definitely worth inlining functions that take lambdas as parameters in order to avoid the creation of these components:
//XXX: fun takes a lambda but performance is not affected because we're inlining it
inline fun registerListener(listener : (View) -> Unit) {}
Reified type parameters
As many other languages, Kotlin applies type erasure. So things may get cumbersome when working with generics: type information is not available at runtime because it has been erased previously. As a result, code like this…
fun<T> manageList(list : List<T>) {
when (list) {
is List<String> -> {
//XXX: do something with strings...
}
is List<Int> -> {
//XXX: do something with ints...
}
else -> {
}
}
}
…will never work!
But this limitation can be overcome by using reified parameters. If we want to mantain the type parameter information at runtime, then we have to apply the reified modifier to the corresponding type argument.
//XXX: apply reified on type param. Do we need something else...?
fun<T> manageList(list : List<reified T>) {
}
Take into account the following considerations:
- reified types can only be used with inline functions. Why? As we said before, when inlining a function, every call to it will be replaced with the function body. When the compiler starts “copy-pasting”, it can extract the type used as argument from the calling code and preserved it when replacing the invocation with the actual implementation.
//XXX: do not forget to make an inline fun too!
inline fun<T> manageList(list : List<reified T>) {
}
- reified types are not compatible with functions invocations from Java code.
By the way, Dart uses reified type params by default, so you don’t have to set them explicitly.
Wrapping up…
Inline functions improve performance and its type parameters can be reified so they become accessible at runtime. So keep them in mind when working with lambdas or if you want to perform type checks during execution.
As usual, check the following link to play around with some inline functions:
Write you next time!