when to use an inline function in Kotlin?

余生长醉 提交于 2019-11-29 18:56:24

Let's say you create a higher order function that takes a lambda of type () -> Unit (no parameters, no return value), and executes it like so:

fun nonInlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

In Java parlance, this will translate to something like this (simplified!):

public void nonInlined(Function block) {
    System.out.println("before");
    block.invoke();
    System.out.println("after");
}

And when you call it from Kotlin...

nonInlined {
    println("do something here")
}

Under the hood, an instance of Function will be created here, that wraps the code inside the lambda (again, this is simplified):

nonInlined(new Function() {
    @Override
    public void invoke() {
        System.out.println("do something here");
    }
});

So basically, calling this function and passing a lambda to it will always create an instance of a Function object.


On the other hand, if you use the inline keyword:

inline fun inlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

When you call it like this:

inlined {
    println("do something here")
}

No Function instance will be created, instead, the code around the invocation of block inside the inlined function will be copied to the call site, so you'll get something like this in the bytecode:

System.out.println("before");
System.out.println("do something here");
System.out.println("after");

In this case, no new instances are created.

Let me add: "When not to use inline":

1) If you have a simple function that doesn't accept other functions as an argument, it does not make sense to inline them. IntelliJ will warn you:

Expected performance impact of inlining '...' is insignificant. Inlining works best for functions with parameters of functional types

2) Even if you have a function "with parameters of functional types", you may encounter the compiler telling you that inlining does not work. Consider this example:

inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
    val o = operation //compiler does not like this
    return o(param)
}

This code won't compile with the error:

Illegal usage of inline-parameter 'operation' in '...'. Add 'noinline' modifier to the parameter declaration.

The reason is that the compiler is unable to inline this code. If operation is not wrapped in an object (which is implied by inline since you want to avoid this), how can it be assigned to a variable at all? In this case, the compiler suggests making the argument noinline. Having an inline function with a single noinline function does not make any sense, don't do that. However, if there are multiple parameters of functional types, consider inlining some of them if required.

So here are some suggested rules:

  • You can inline when functional type param is called directly or passed to other inline function
  • You should inline when ^ is the case.
  • You cannot inline when function parameter is being assigned to variable inside function
  • You should consider inlining if at least one of your functional typed parameters can be inlined, use noinline for the rest.
  • You should not inline huge functions, think about generated byte code. It will be copied to all places the function is called from.
  • Another use case is reified type parameters, which require you to use inline. Read here.

The most important case when we use inline modifier is when we define util-like functions with parameter functions. Collection or string processing (like filter, map or joinToString) or just standalone functions are perfect example.

This is why inline modifier is mostly an important optimization for library developers. They should know how does it work and what are its improvements and costs. We will use inline modifier in our projects when we define our own util functions with function type parameters.

When we don’t have function type parameter, reified type parameter, and we don’t need non-local return, then we most likely shouldn’t use inline modifier. This is why we will have a warning on Android Studio or IDEA IntelliJ.

Also there is the code size problem. Inlining a large function could dramatically increase the size of the bytecode because it's copied to every calls site. In such cases, you can refactor the function and extract code to regular functions.

One simple case where you might want one is when you create a util function that takes in a suspend block. Consider this.

fun timer(block: () -> Unit) {
    // stuff
    block()
    //stuff
}

fun logic() { }

suspend fun asyncLogic() { }

fun main() {
    timer { logic() }

    // This is an error
    timer { asyncLogic() }
}

In this case, our timer won't accept suspend functions. To solve it, you might be tempted to make it suspend as well

suspend fun timer(block: suspend () -> Unit) {
    // stuff
    block()
    // stuff
}

But then it can only be used from coroutines/suspend functions itself. Then you'll end up making an async version and a non-async version of these utils. The problem goes away if you make it inline.

inline fun timer(block: () -> Unit) {
    // stuff
    block()
    // stuff
}

fun main() {
    // timer can be used from anywhere now
    timer { logic() }

    launch {
        timer { asyncLogic() }
    }
}

Here is a kotlin playground with the error state. Make the timer inline to solve it.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!