skip to content

callsInPlace Effects

callsInPlace promises how often a lambda runs, which is why you can initialize a val inside a run or let block and have the compiler accept it. Without that promise the compiler must assume the lambda might run zero or many times.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

Why can you assign to a val exactly once inside a `run { }` lambda and have the compiler accept it as definitely initialized afterwards? What language feature makes this work?

level: juniorimportance: must knowfreq 55%

answer

  1. run/let/with carry EXACTLY_ONCE contracts
  2. callsInPlace = invoked in place + how many times
  3. enables definite-assignment of val in lambda
  4. without contract: lambda assumed 0..many times
  5. stdlib declares it; consumer needs no opt-in

basics

~20 s

The standard-library functions like run and let promise the compiler that the lambda you pass runs exactly once. Because of that promise, the compiler knows a val you set inside the lambda gets set once, so it treats it as initialized.

solid answer

~40 s

Scope functions such as `run`, `let`, `with`, and `apply` carry a Kotlin *contract* declared with the `contracts` block and `callsInPlace(block, InvocationKind.EXACTLY_ONCE)`. This tells the compiler the lambda parameter is invoked in place, synchronously, exactly once before the function returns. With that guarantee the compiler's definite-assignment analysis can prove a `val` written once inside the lambda is assigned exactly once — so it is initialized after the call and can be referenced, and the `val` cannot be re-assigned. Without the contract the compiler would see the lambda as possibly never called (or called many times) and reject `val` assignment inside it, forcing you to use a different pattern. The contract is in the stdlib source, so you get this for free using these functions.

code

kotlin · 6 lines
kotlin
val name: String
run {
    val raw = readLine().orEmpty()
    name = raw.trim()   // assigning a val from inside the lambda — allowed
}
println(name)            // definitely initialized here

go deeper

for a junior

Knows that you can set a val inside run/let and use it afterwards, and that it's something special these functions provide.

for a middle

Names the contract and EXACTLY_ONCE, and explains definite-assignment analysis as the mechanism.

for a senior

Can reproduce the stdlib contract declaration and explain why a custom function without it fails; distinguishes 'in place' from invocation count.

for a principal

Frames it as how compile-time effects extend the type system's flow analysis, and discusses the experimental status and stability trade-offs of authoring such contracts.

## The problem A `val` (read-only local) in Kotlin must be assigned **exactly once** along every path. The compiler runs *definite-assignment analysis* to prove this. When you pass a lambda to a normal higher-order function, the compiler cannot know whether or how many times that lambda runs, so it conservatively assumes it might run **zero or many** times. Writing a `val` inside such a lambda would then look like "maybe never assigned" or "maybe assigned twice" — both illegal. ```kotlin fun myOwn(block: () -> Unit) { block() } val x: Int // without a contract, this fails myOwn { x = 42 } // error: captured val cannot be assigned / not initialized println(x) // error: variable might not be initialized ``` ## The fix: callsInPlace Kotlin **contracts** let a function describe its behavior to the compiler. The relevant effect is `callsInPlace`: ```kotlin import kotlin.contracts.InvocationKind import kotlin.contracts.contract @OptIn(ExperimentalContracts::class) inline fun <R> myRun(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } ``` `callsInPlace(block, kind)` promises two things: - **In place**: the lambda is invoked *synchronously*, before the enclosing function returns — not stored, not run later on another thread. - **Kind**: how many times — here `EXACTLY_ONCE`. With `EXACTLY_ONCE`, the compiler now reasons: the lambda runs once, so a `val` assigned inside runs once. Definite-assignment passes: ```kotlin val x: Int myRun { x = 42 } // OK: assigned exactly once println(x) // OK: definitely initialized ``` ## The real stdlib functions The standard scope functions already declare this. Their source contains, for example: ```kotlin public inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } ``` So `run`, `let`, `with`, `also`, `apply`, and `repeat` (which uses `AT_LEAST_ONCE`) all benefit from it. That's why this pattern works without you writing any contract yourself. ## Key terms - **Contract**: compile-time-only metadata describing a function's effect; defined in a `contract { }` block as the first statement of the body. - **Definite assignment**: the compiler proof that a `val`/`var` is set before use. - **InvocationKind**: enum — `EXACTLY_ONCE`, `AT_MOST_ONCE`, `AT_LEAST_ONCE`, `UNKNOWN`. Note contracts are still marked experimental (`@ExperimentalContracts`) for *authoring*, but consuming the stdlib's contracts requires no opt-in.

  • Would the same val assignment compile if you wrote your own `fun apply2(block: () -> Unit)` without a contract?
    No. Without `callsInPlace`, the compiler assumes the lambda may run zero or many times, so it rejects the val assignment and treats the variable as possibly uninitialized.
  • Does this require the function to be `inline`?
    The stdlib functions are inline, but `callsInPlace` itself works for non-inline functions too; it just describes invocation, independent of inlining.

It's a signed promise note: the function swears 'I'll call your lambda exactly once, right now' so the compiler trusts a one-time val assignment inside it.

saying these in an interview costs you the question

  • Says it works 'because run is inline' (inlining is unrelated to the assignment proof)
  • Thinks any higher-order function allows val assignment inside its lambda
  • Confuses this with smart casts / `returns` effects
  • Believes the lambda result is what makes it work, not the contract
  • Claims you must add @ExperimentalContracts to just call run

context

open as a page

Write a custom inline function that wraps a block and lets callers initialize a `val` from inside its lambda. What is the exact `contract { }` declaration, what opt-in is needed, and what placement rules apply?

level: middleimportance: should knowfreq 35%

basics

~10 s

Put a contract as the very first statement of the function and call callsInPlace(block, EXACTLY_ONCE). Mark it with the experimental contracts opt-in. Then callers can set a val inside the lambda.

open as a page

Explain the four `InvocationKind` values used with `callsInPlace` and what each one permits the compiler to assume about a `val` (and a `var`) assigned inside the lambda.

level: middleimportance: should knowfreq 45%

basics

~20 s

The kind says how often the lambda runs: exactly once, at most once (zero or one), at least once (one or more), or unknown. Only 'exactly once' lets you assign a val and use it afterward as initialized.

open as a page

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?

level: seniorimportance: should knowfreq 28%

basics

~20 s

callsInPlace 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.

open as a page

Which standard-library functions rely on `callsInPlace` contracts, what invocation kind does each use, and how does that affect what you can write inside their lambdas?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Scope functions like run, let, with, also, apply use 'exactly once', so you can set a val inside them. repeat uses 'at least once', so a val can't be set there but a var can.

open as a page