Inline Function — When to use?
In this article, we will investigate the Kotlin inline function in detail and come to a conclusion on when and why to use the inline function.
What are inline functions?
To make the function inline, we will add the inline
keyword before fun
to convert normal function to inline function
inline fun calculateTime() {
println("Calculate")
}
fun main() {
println("Start")
calculateTime()
println("End")
}
Decompiled:
public void main() {
System.out.println("Start");
System.out.println("Calculate");
System.out.println("End");
}
As we can see complete body of inline function gets inserted at function call site when decompiled.
Inline function are functions whose body gets copied to the calling place
Advantage of Inline function: No function call overhead — Faster program execution.
Why not make every function inline?
Making Every function inline eventually grow the code as same code will repeat everywhere.
Some will say, if function body is small use inline otherwise not. This is correct upto some extent.
But when we inline a normal function with normal parameters, compiler will give us the below warning
Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types
So this means expected impact of inlining on performance is negligible as most probably compiler will do it if required.
When do we inline the function?
We generally prefer to inline a function when function take functional type parameters (lambdas)
Let’s dive into the reason for this and how inlining actually help us in this case
In normal case:
fun calculateTime(block: ()->Unit): Long {
val initialTime = System.currentTimeMillis()
block.invoke()
return System.currentTimeMillis() - initialTime
}
fun main() {
val time = calculateTime {
println("Hello")
}
println(time)
}
Decompiled:
public long calculateTime(Function0 block) {
long initialTime = System.currentTimeMillis();
block.invoke();
return System.currentTimeMillis() - initialTime;
}
public void main() {
long time = calculateTime(
new Function() {
@Override
public void invoke() {
System.out.print("Hello");
}
}
);
System.out.println(time);
}
public interface Function0<out R> : Function<R> {
public operator fun invoke(): R
}
In Kotlin, function types (lambdas) are converted to object of anonymous/regular classes(Function0
) that extend the interface Function
Problem? — If we call this function (calculateTime
) 100 times, 100 object of Function
class will be created and garbage collected. This affects performance.
Solution? — Use inline for preventing object creation
inline fun calculateTime(block: ()->Unit): Long {
val initialTime = System.currentTimeMillis()
block.invoke()
return System.currentTimeMillis() - initialTime
}
fun main() {
val time = calculateTime {
println("Hello")
}
println(time)
}
Decompiled:
public void main() {
long initialTime = System.currentTimeMillis();
System.out.println("Hello");
long time = System.currentTimeMillis() - initialTime;
System.out.println(time);
}
So, the functions that take other functions as arguments are faster when they are inlined (since no Function
objects are created).
We should use inline function when we have functional type parameter with small function body.
noinline
What if we have multiple functional type parameter in the inline function and we do not want to inline all the parameters, we can use noinline
keyword.
fun main() {
val time = calculateTime({
println("Hellow")
}, {
println("World")
})
println(time)
}
inline fun calculateTime(block1: () -> Unit, noinline block2: () -> Unit): Long {
val initialTime = System.currentTimeMillis()
block1.invoke()
block2.invoke()
return System.currentTimeMillis() - initialTime
}
Decompiled code:
public void main() {
long initialTime = System.currentTimeMillis();
System.out.println("Hello");
Function block = new Function() {
@Override
public void invoke() {
System.out.print("World");
}
}
);
block.invoke();
long time = System.currentTimeMillis() - initialTime;
System.out.println(time);
}
crossinline
crossinline
keyword is used to avoid non-local returns.
Let’s understand with example
inline fun calculateTime(block: () -> Unit): Long {
val initialTime = System.currentTimeMillis()
block.invoke()
return System.currentTimeMillis() - initialTime
}
fun main() {
val time = calculateTime {
println("Hello")
return
}
println(time)
}
Decompiled:
public void main() {
long initialTime = System.currentTimeMillis();
System.out.println("Hello");
}
Here we can see that after return, no other statement is written (calculating and printing final time). This is non-local return. To avoid this issue, we can mark the lambda as crossinline
.
inline fun calculateTime(crossinline block: () -> Unit): Long {
val initialTime = System.currentTimeMillis()
block.invoke()
return System.currentTimeMillis() - initialTime
}
fun main() {
val time = calculateTime {
println("Hello")
return // This will give compile time error
}
println(time)
}
When mark lambda parameter as crossinline
, if we add return statement, compiler will give the error (‘return’ is not allowed here
).
Use inline for reified type parameters
What if we want to work with class type directly in Kotlin generics?
fun <T> doSomething(value: T) {
println("Value: $value") // OK
println("Type: ${T::class.simpleName}") // Error
}
fun main() {
doSomething("something")
}
In the above example we get the error
Cannot use ‘T’ as reified type parameter. Use a class instead
We cannot work with the type directly, because the type argument gets erased at runtime when we pass to the function. So, we cannot possibly know exactly which type we are handling.
Solution? — Use an inline function along with the reified type parameter.
inline fun <reified T> doSomething(value: T) {
println("Value: $value") // OK
println("Type: ${T::class.simpleName}") // OK
}
fun main() {
doSomething("something")
}
In the above example actual type argument will be copied in place of T. So, T::class.simpleName
becomes String::class.simpleName
.
The reified keyword can only be used with inline functions.
Source Code: Github
Contact Me:
Happy Coding ✌️