Inline lambda functions have an important restriction—they can't be manipulated in any way (stored, copied, and others).
The UserService stores a list of listeners (User) -> Unit:
data class User(val name: String)
class UserService {
val listeners = mutableListOf<(User) -> Unit>()
val users = mutableListOf<User>()
fun addListener(listener: (User) -> Unit) {
listeners += listener
}
}
Changing addListener into an inline function will produce a compilation error:
inline fun addListener(listener: (User) -> Unit) {
listeners += listener //compilation error: Illegal use of inline-parameter listener
}
If you think about it, it makes sense. When we inline a lambda, we're replacing it for its body, and that isn't something that we can store on a Map.
We can fix this problem with the noinline modifier:
//Warning: Expected performance impact of inlining addListener can be insignificant
inline fun addListener(noinline listener: (User) -> Unit) {
listeners += listener
}
Using noinline on an inline function will inline just the high-order function body but not the noinline lambda parameters (an inline high-order function can have both: inline and noinline lambdas). The resulting bytecode isn't as fast as a fully inline function, and the compiler will show a warning.
Inline lambda functions can't be used inside another execution context (local object, nested lambda).
In this example, we can't use transform inside the buildUser lambda:
inline fun transformName(transform: (name: String) -> String): List<User> {
val buildUser = { name: String ->
User(transform(name)) //compilation error: Can't inline transform here
}
return users.map { user -> buildUser(user.name) }
}
To fix this problem, we need a crossinline modifier (alternatively, we can use noinline but with the associated performance lost):
inline fun transformName(crossinline transform: (name: String) -> String): List<User> {
val buildUser = { name: String ->
User(transform(name))
}
return users.map { user -> buildUser(user.name) }
}
fun main(args: Array<String>) {
val service = UserService()
service.transformName(String::toLowerCase)
}
The generated code is quite complex. Many pieces are generated:
- A class that extends (String) -> User to represent buildUser and internally creates User using String::toLowerCase to transform the name
- A normal inline code to execute List<User>.map() using an instance of the class that represents buildUser
- List<T>.map() is inline, so that code gets generated too
Once you're aware of its restrictions, inline high-order functions are a great way to increase the execution speed of your code. Indeed, a lot of the high-order functions inside the Kotlin Standard Library are inline.