Why is a bare `return` allowed inside a lambda passed to an `inline` function but a compile error inside a non-inline one?
answer
- Inline lambda has no separate frame => return has a real target
- Non-inline lambda = Function object, may outlive caller
- Error message: 'return' is not allowed here
- Labelled return is always legal
- crossinline forbids non-local return but keeps inlining
basics
~20 sAn inline function's lambda is pasted directly into your code, so a return there is really inside your function and can return from it. A non-inline lambda is a separate object that might run later, so there is nothing valid to return from — hence the error.
solid answer
~50 sWith `inline`, the compiler substitutes the lambda body at the call site, so the lambda has no separate stack frame; a bare `return` therefore has a well-defined target — the enclosing function — and is allowed. With a non-inline higher-order function the lambda is compiled to a `Function` instance whose `invoke` may be stored, deferred, or run on another thread. At the point the `return` would execute, the enclosing function may have already returned, so there is no live frame to return from. Kotlin makes this a compile-time error: "'return' is not allowed here." The only legal returns inside a non-inline lambda are labelled returns (`return@label`) targeting the lambda itself. This is why `crossinline` exists: it keeps a parameter inlined for performance but forbids non-local returns, e.g. when the lambda is passed to another execution context.
code
kotlin · 7 linesinline fun inlined(block: () -> Unit) = block()
fun notInlined(block: () -> Unit) = block()
fun caller() {
inlined { return } // OK: returns from caller
notInlined { return } // compile error: 'return' is not allowed here
}go deeper
Recognizes that inline allows the return and non-inline does not, even if fuzzy on why.
Explains the separate-Function-object reasoning and the stack-frame safety argument.
Connects the rule to crossinline/noinline and articulates the compile-time safety guarantee.
Reasons about how the constraint shapes API design for deferred/async callbacks vs. immediate inline ones.
## The mechanism difference ### Inline path Declaring a higher-order function `inline` instructs the compiler to **replace the call with the function's body**, and to **paste each lambda argument's body** where the parameter is invoked. No `Function` object is allocated. Because the lambda code now lives literally inside the caller, a bare `return` compiles to an ordinary return from the **enclosing function** — a *non-local return*. ```kotlin inline fun forEachInt(xs: IntArray, action: (Int) -> Unit) { for (x in xs) action(x) } fun find(xs: IntArray): Int { forEachInt(xs) { if (it > 0) return it } // OK: returns from find return -1 } ``` ### Non-inline path Without `inline`, the lambda becomes a **first-class object** (an implementation of `Function1`, `Function0`, etc.). Its `invoke` could be: - stored in a field and called later, - passed to another thread, - never called at all. At the moment a non-local `return` would run, the enclosing function's stack frame **may no longer exist**. Kotlin cannot safely implement "return from a frame that may be gone," so it **rejects a bare `return`** at compile time: ```kotlin fun runLater(block: () -> Unit) { /* maybe stores block */ } fun demo() { runLater { return } // error: 'return' is not allowed here } ``` ## What is still allowed Inside any lambda you may always use a **labelled return** that targets the lambda, because that target always exists: ```kotlin runLater { return@runLater } // legal: leaves only the lambda ``` ## Related keywords - **`inline`** — enables non-local return. - **`crossinline`** — marks an inlined lambda that must **not** perform non-local returns (e.g., it is invoked from a nested object or another context). The compiler then forbids bare `return` in it while still inlining it. - **`noinline`** — opts a specific lambda parameter out of inlining entirely; it becomes a normal object, so non-local return is again disallowed for it. ## Why the design is safe The rule guarantees a non-local `return` can never try to unwind a stack frame that has already been popped — it is a compile-time guarantee, not a runtime check.
- How can you keep a lambda inlined for performance yet forbid non-local returns?Mark the parameter `crossinline`. It stays inlined but a bare `return` in it becomes a compile error; only `return@label` is allowed.
- What happens if you mark a parameter `noinline`?That lambda is no longer inlined — it becomes a real Function object — so non-local returns from it are disallowed just like any non-inline HOF.
saying these in an interview costs you the question
- Saying non-inline returns are a runtime exception, not a compile error
- Believing the JVM supports returning from a popped frame
- Confusing crossinline with noinline
- Thinking inlining is just a JIT optimization rather than a Kotlin compiler transform
- Claiming labelled returns are also forbidden in non-inline lambdas