Inline Functions, reified, and Value Classes
Prerequisites: Generics, Lambdas and higher-order functions
These features trade a little extra compiled code for speed and type safety.
inline functions
Passing a lambda normally creates an object for it. Marking a
higher-order function inline makes the compiler copy the function body
— and the lambda — directly into each call site instead:
inline fun <T> measure(label: String, block: () -> T): T {
val start = System.nanoTime()
val result = block()
val ms = (System.nanoTime() - start) / 1_000_000
println("$label finished (${if (ms < 1000) "under a second" else "slow"})")
return result
}
fun main() {
val total = measure("sum") { (1..1_000).sum() }
println(total)
}
sum finished (under a second)
500500
Most of the standard library's collection functions (map, filter,
forEach) are inline, which is why they cost no more than hand-written
loops. A side effect: a plain return inside an inlined lambda returns
from the enclosing function.
Only inline small functions that take lambdas — inlining large functions just bloats the compiled code.
reified type parameters
Normally, generic type information is erased at runtime: inside
fun <T> f(), you can't check is T. An inline function can mark its
type parameter reified, keeping the real type available:
inline fun <reified T> List<Any>.firstOfType(): T? =
filterIsInstance<T>().firstOrNull()
fun main() {
val mixed: List<Any> = listOf("a", 1, 2.5, "b")
println(mixed.firstOfType<Double>())
println(mixed.firstOfType<Int>())
println(mixed.firstOfType<Char>())
}
2.5
1
null
filterIsInstance<T>() itself is a reified inline function — as are
library APIs like Json.decodeFromString<User>(...).
Value classes
A value class wraps a single value to give it its own type, usually with no runtime cost — the compiler uses the underlying value directly where it can:
@JvmInline
value class UserId(val value: Long)
@JvmInline
value class OrderId(val value: Long)
fun findOrder(id: OrderId) = "Order #${id.value}"
fun main() {
val order = OrderId(42)
println(findOrder(order))
println(order)
}
Order #42
OrderId(value=42)
Passing a UserId to findOrder is now a compile error — even though
both are "just a Long" underneath. Value classes make whole categories
of mix-ups impossible: user IDs vs order IDs, meters vs feet, cents vs
dollars. They can also validate in an init block.
Common mistakes
- Marking functions
inlinethat don't take lambdas. The compiler warns that the performance benefit is insignificant.