A teammate adds `callsInPlace(block, InvocationKind.EXACTLY_ONCE)` to a function that actually stores `block` in a field and invokes it later on a background thread. Why is this dangerous, and what guarantees does `callsInPlace` really make?
answer
- callsInPlace = in place (synchronous, non-escaping) + count
- trusted, not verified — no compiler warning
- escaping lambda → uninitialized read / NPE / data race
- inline non-crossinline lambdas can't escape (caught early)
- escaping? use no callsInPlace at all, not UNKNOWN
basics
~20 scallsInPlace promises the lambda runs right now, synchronously, inside the call — not stored or run later. Lying about that lets the compiler assume a val is safely set when it really isn't, causing crashes or uninitialized reads.
solid answer
~50 s`callsInPlace` makes two promises: the lambda is invoked **in place** (synchronously, within the function call, before it returns and not retained), and it is invoked according to the declared `InvocationKind` count. If the function instead stores `block` and runs it later on another thread, both promises are broken. The compiler, trusting the contract, will let callers assign a `val` inside that lambda and then read it right after the call as if initialized. At runtime the lambda hasn't run yet, so the `val`/captured variable is uninitialized — for a non-null reference type you get a `NullPointerException` (or reads of a zero/garbage value), and you also get data races since the deferred execution touches caller state concurrently. Contracts are **trusted, not verified**, so the compiler emits no warning. The fix is to use `InvocationKind.UNKNOWN` only if it's still synchronous, or drop `callsInPlace` entirely when the lambda escapes.
code
kotlin · 11 lines// WRONG: lambda escapes but contract claims EXACTLY_ONCE
@OptIn(ExperimentalContracts::class)
fun schedule(block: () -> Unit) {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
executor.submit(block) // deferred -> breaks 'in place' promise
}
// RIGHT: no callsInPlace, because the lambda is not called in place
fun schedule(block: () -> Unit) {
executor.submit(block)
}go deeper
May only know EXACTLY_ONCE 'runs once' and miss the in-place/escaping dimension.
Identifies that the lambda must run synchronously and that lying causes uninitialized reads.
Explains both guarantees, the trusted-not-verified nature, the NPE/data-race outcomes, and that escaping means no callsInPlace at all.
Connects it to memory-model happens-before, inline/crossinline escape rules, and how to lint or test for contract honesty in a shared codebase.
## What callsInPlace actually guarantees `callsInPlace(block, kind)` asserts: 1. **In place / non-escaping**: the lambda is called *during* this function's execution, synchronously, and is not stored, returned, or deferred to run after the function returns or on another thread. 2. **Count**: how many times, per `InvocationKind`. Both are *promises the author makes to the compiler*. The compiler does not check them; it propagates the consequences into the caller's flow analysis. ## The bug ```kotlin private var stored: (() -> Unit)? = null @OptIn(ExperimentalContracts::class) inline fun register(block: () -> Unit) { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } // LIE stored = block // escapes! thread { stored?.invoke() } // runs later, off-thread } ``` Caller: ```kotlin val token: String register { token = makeToken() } println(token.length) // compiler: 'definitely initialized'; runtime: NOT yet ``` The compiler believes `token` is assigned before `println`, because EXACTLY_ONCE says the lambda already ran. In reality it runs later on a background thread. Consequences: - **Uninitialized read**: `token` may be read before assignment → for a non-null `String` this surfaces as a `NullPointerException` or reading default/garbage state. - **Data race**: the background thread writes `token` while the main thread reads it — no happens-before relationship, undefined results. - **No diagnostics**: contracts are *trusted*; the unsound analysis is silent. ## Why `inline` makes escaping illegal anyway If `register` is `inline` and `block` is not marked `noinline`/`crossinline`, the compiler forbids storing it in a field at all (inline lambdas can't escape). So this exact bug often requires `noinline`/`crossinline` or a non-inline function — which is the real smell: an escaping lambda combined with a `callsInPlace` contract. ## Correct choices - If the lambda is genuinely synchronous but you can't promise a count, use `InvocationKind.UNKNOWN` (still asserts in-place). - If the lambda escapes (stored, deferred, async), **do not use `callsInPlace` at all** — there is no valid kind, because the in-place premise itself is false. ## Mental model Think of `callsInPlace` as a contract clause: 'I will run your code right here, right now.' Storing it for later breaks the clause, and because no one audits the clause, the breach only shows up as runtime corruption.
- Would marking the function inline have prevented this mistake?Often yes: a plain inline lambda can't be stored in a field, so the compiler rejects the escape. The bug typically needs noinline/crossinline or a non-inline function.
- If the lambda is synchronous but you can't promise a count, what kind do you use?InvocationKind.UNKNOWN — it still asserts in-place execution without committing to a number; it just won't enable val assignment or guaranteed-initialized reads.
It's like signing a delivery receipt before the package arrives — the paperwork says 'received', but the box is still on a truck somewhere.
saying these in an interview costs you the question
- Thinks callsInPlace only describes count, not synchronous/in-place execution
- Believes the compiler verifies the contract at compile time
- Suggests using UNKNOWN for a lambda that actually escapes
- Ignores the data-race / happens-before problem entirely
- Assumes uninitialized val just defaults silently with no consequence