Skip to content
AgileLogics
6 min read

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 inline that don't take lambdas. The compiler warns that the performance benefit is insignificant.