Inline Function — When to use?

khushpanchal123

Khush Panchal

Posted on April 29, 2024

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")
}
Enter fullscreen mode Exit fullscreen mode

Decompiled:

public void main() {
   System.out.println("Start");
   System.out.println("Calculate");
   System.out.println("End");
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode
public interface Function0<out R> : Function<R> {
    public operator fun invoke(): R
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Decompiled:

public void main() {
  long initialTime = System.currentTimeMillis();
  System.out.println("Hello");
  long time = System.currentTimeMillis() - initialTime;
  System.out.println(time);
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Decompiled code:

public static final 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);
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Decompiled:

public static final void main() {
   long initialTime = System.currentTimeMillis();
   System.out.println("Hello");
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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:
LinkedIn, Twitter

Happy Coding ✌️

💖 💪 🙅 🚩
khushpanchal123
Khush Panchal

Posted on April 29, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related