Inline Functions

The Cost of Lambdas

Each lambda passed to a normal higher-order function becomes an object, and calling it is a virtual call. In hot loops this adds allocations. Marking the function inline makes the compiler copy the function body and the lambda body into the call site: no lambda object, no extra call.

inline fun measure(block: () -> Unit) { val t = now(); block(); log(now() - t) }

measure { work() }        ─compiles to─►   val t = now(); work(); log(now() - t)

Non-local return

Example

Because the lambda is copied into the caller, a plain return inside it returns from the enclosing function. This is why return works inside forEach.

inline fun <T> List<T>.eachItem(action: (T) -> Unit) {
    for (item in this) action(item)
}

fun findFirstExpensive(prices: List<Int>): Int? {
    prices.eachItem { p ->
        if (p > 1000) return p          // returns from findFirstExpensive
        println("checked $p")
    }
    return null
}

fun main() {
    println("Result: ${findFirstExpensive(listOf(200, 650, 1500, 3000))}")

    // a labelled return only leaves the lambda
    listOf(1, 2, 3).forEach {
        if (it == 2) return@forEach
        println("forEach item $it")
    }
}

Output

checked 200
checked 650
Result: 1500
forEach item 1
forEach item 3

When to Inline

  • Small functions that take lambdas: let, apply, filter, map, use, synchronized, measureTimeMillis are all inline.
  • Do not inline big functions or functions without lambda parameters: the code is copied everywhere and the app gets bigger. The compiler warns you.
  • Inline functions cannot be recursive and cannot access private members of their class from a public inline function.