What does the crossinline modifier do on a lambda parameter of an inline function?
answer
- Inlined but no non-local return
- Needed when lambda runs in another context (Runnable/nested lambda)
- Only return@label allowed inside
- Middle ground: inline < crossinline < noinline
- Compiler error without it when capturing the lambda
basics
~20 scrossinline marks a lambda of an inline function so it cannot use a non-local return. It is still inlined, but you are not allowed to write 'return' to exit the calling function from inside it.
solid answer
~40 sIn an inline function, lambda parameters are inlined into the call site, which lets them use non-local returns: a plain 'return' inside the lambda returns from the enclosing function. crossinline keeps the lambda inlined but forbids that non-local return. You apply it when the lambda is not called directly inside the inline function body but is instead passed into another execution context, for example stored in a Runnable, an object, or a nested lambda, where allowing a return-from-caller would be impossible or unsafe. The lambda body is still inlined (no separate function object created for the simple path), so you keep inlining benefits, but only local 'return@label' returns are permitted. It is the middle ground between a normal inline lambda (non-local return allowed) and noinline (not inlined at all).
code
kotlin · 11 linesinline fun runLater(crossinline body: () -> Unit) {
val r = Runnable { body() } // body invoked in another execution context
r.run()
}
fun demo() {
runLater {
// return // ERROR: non-local return forbidden
return@runLater // OK: local return only
}
}go deeper
Can state that crossinline keeps the lambda inlined but blocks non-local returns and is needed when the lambda is captured elsewhere.
Explains the call-site-copying mechanism behind non-local returns and why capturing into a Runnable breaks it.
Contrasts crossinline vs noinline vs plain inline precisely and picks the right one for a given indirect-invocation scenario.
Reasons about codegen and API ergonomics: when to expose crossinline vs noinline in a public inline DSL to balance inlining wins against caller flexibility.
## The problem crossinline solves When a function is marked `inline`, the compiler copies the function body and its lambda arguments directly into the call site. Because the lambda body ends up physically inside the caller, a bare `return` inside that lambda can return from the **enclosing function** — this is called a **non-local return**. ```kotlin inline fun forEachItem(list: List<Int>, action: (Int) -> Unit) { for (item in list) action(item) } fun find(list: List<Int>): Int { forEachItem(list) { item -> if (item > 10) return item // non-local return: returns from find() } return -1 } ``` This works because `action` is invoked **directly** inside `forEachItem`'s own body. But sometimes you don't call the lambda directly — you pass it somewhere else (another lambda, a `Runnable`, an anonymous object). In that case a non-local return is impossible: by the time the lambda runs, the caller's stack frame may be gone. ## What crossinline does `crossinline` tells the compiler: "keep inlining this lambda, but **forbid non-local returns** from it." The lambda is still inlined (so you avoid allocating a function object on the simple path), but only **local returns** (`return@label`) are allowed. ```kotlin inline fun runLater(crossinline body: () -> Unit) { val r = Runnable { body() } // body used in another execution context r.run() } ``` Without `crossinline`, the compiler would reject `Runnable { body() }` because the lambda is captured into another context where a non-local return cannot be honored. With `crossinline` the code compiles, and any caller's lambda may only do local returns: ```kotlin runLater { // return // COMPILE ERROR: 'return' not allowed here return@runLater // OK: local return, exits only this lambda } ``` ## How it differs from the alternatives - **Plain inline lambda** — inlined, non-local return allowed (the default for inline-function lambdas). - **crossinline lambda** — inlined, non-local return **forbidden**; needed when the lambda runs in a different execution context. - **noinline lambda** — **not** inlined at all; becomes a real function object you can store/pass freely; non-local return also impossible. ## Key keywords and APIs involved - `inline`, `crossinline`, `noinline` modifiers - non-local `return` vs local `return@label` - function types like `() -> Unit`, used as `Runnable`, captured in nested lambdas or anonymous objects ## Mental rule Use `crossinline` when you want the lambda inlined **and** you must use it indirectly (inside a `Runnable`, a callback object, a nested lambda). If you instead need to store it in a field or pass it onward as a value, use `noinline`.
- Is a crossinline lambda still inlined?Yes. crossinline only removes the non-local return capability; the body is still inlined into the call site, so you keep inlining benefits.
- What return is still legal inside a crossinline lambda?A labeled local return such as return@functionName, which exits only the lambda itself.
Like a guest you bring into a room but who isn't allowed to flip the building's main breaker — they can only switch off the lamp in their own corner.
saying these in an interview costs you the question
- Saying crossinline turns off inlining (it does not — that's noinline)
- Claiming crossinline allows non-local returns
- Confusing crossinline with noinline
- Believing crossinline is about performance rather than control flow
- Thinking it forbids all returns, including local return@label