These features explain the zero-cost abstractions of the standard library and let you write type-safe IDs cheaply.
Every lambda passed to a normal function becomes an object (and capturing lambdas allocate on each call), plus a virtual call. An inline function avoids this: the compiler copies the function body and the lambda's code into each call site.
inline fun <T> measure(block: () -> T): T { val t = System.nanoTime(); return block().also { println(System.nanoTime() - t) } }Effects of inlining:
- No lambda allocation or call overhead; this is why
map,filter,let,usecost nothing extra. - Non-local returns: a bare
returninside an inlined lambda returns from the enclosing function. - Reified type parameters become possible because the actual type is known at each call site.
Modifiers on lambda parameters: noinline keeps one lambda as an object (so you can store or pass it on); crossinline forbids non-local returns, required when the lambda is called from another context such as a nested object or another lambda. Inline only small functions that take lambdas; inlining big bodies bloats bytecode.
A value class wraps a single value with a distinct type, usually without allocation:
@JvmInline value class UserId(val raw: Long)At runtime a UserId is mostly just a long, but the compiler won't let you pass an OrderId where a UserId is expected. It gets boxed when used as a nullable, generic or interface type. Names of functions taking value classes are mangled in bytecode, which matters for Java callers.
Example
@JvmInline value class UserId(val raw: Long) { init { require(raw > 0) } }
@JvmInline value class OrderId(val raw: Long)
fun loadUser(id: UserId) = "user #${id.raw}"
inline fun <T> timed(label: String, block: () -> T): T {
val start = System.nanoTime()
return block().also { println("$label took ${(System.nanoTime() - start) / 1_000_000} ms") }
}
inline fun runTwice(crossinline action: () -> Unit) {
val r = Runnable { action() } // called from another context
r.run(); r.run()
}
fun firstNegative(xs: List<Int>): Int? {
xs.forEach { if (it < 0) return it } // non-local return (forEach is inline)
return null
}
fun main() {
println(loadUser(UserId(42))) // user #42
// loadUser(OrderId(42)) // compile error: wrong type
val sum = timed("sum") { (1..1000).sum() } // sum took N ms
println(sum) // 500500
runTwice { print("x") }; println() // xx
println(firstNegative(listOf(3, -1, -5))) // -1
}Remember
- inline copies the function and lambda bodies to the call site: no allocations
- Inlined lambdas allow non-local return and reified types
- noinline keeps a lambda as an object; crossinline forbids non-local returns
- Value classes give type safety for wrapped values, usually without allocation
Watch out for
- Marking large functions without lambda parameters inline, bloating bytecode for no benefit.