skip to content

How does x?.run { } behave with a nullable receiver, and why can't with(x) { } offer the same null guard?

level: middleimportance: should knowfreq 55%

answer

  1. ?. needs a receiver; run is an extension -> x?.run works
  2. with takes an argument -> no receiver -> no ?. guard
  3. null x?.run yields null, type becomes R?
  4. pair x?.run with ?: for fallback
  5. block runs only when x non-null

basics

~20 s

x?.run { } runs the block only when x is not null, otherwise the whole thing is null. with(x) always runs because x is just a parameter, so a null x would make member calls inside crash or need an explicit check.

solid answer

~40 s

run is an extension function, so it composes with the safe-call operator ?.. In x?.run { ... } the runtime checks x for null first; if null, the block is skipped and the expression yields null, otherwise the block runs with x as the non-null this and returns its result. The static type becomes nullable (e.g. R?). with(x) { } is a non-extension top-level function whose first parameter is the object, so there is no receiver for ?. to guard; the call always executes, and if x is nullable you must null-check beforehand or smart-cast. This is the key practical reason to prefer x?.run over with for nullable configure-and-compute: it gives concise, expression-style null handling and short-circuits cleanly, pairing naturally with the Elvis operator ?: for a fallback.

code

kotlin · 3 lines
kotlin
fun port(uri: java.net.URI?): Int =
    uri?.run { if (port == -1) 80 else port } ?: 80
// uri null -> 80; else compute from non-null URI's port

go deeper

for a junior

Knows x?.run skips the block when x is null and yields null.

for a middle

Uses x?.run { } ?: default idiomatically and explains why with lacks a null guard.

for a senior

Reasons about the resulting nullable type and short-circuit semantics, and restructures when side effects must run regardless of null.

for a principal

Guides when concise ?.run chains aid readability vs. when explicit null handling is clearer for the team.

## Safe call meets extension functions The safe-call operator **`?.`** only works on an expression that has a **receiver** — i.e., `someValue?.member`. Because `run` is declared as an **extension function** (`fun <T, R> T.run(block: T.() -> R): R`), the call site is `x.run { }`, where `x` is the receiver. That means you can write **`x?.run { }`**: if `x` is `null`, the safe call short-circuits and the entire expression evaluates to `null`; if `x` is non-null, the block runs with `x` smart-cast to its non-null type as the `this` receiver. ```kotlin fun describe(name: String?): String = name?.run { "len=$length, upper=${uppercase()}" } ?: "<none>" ``` Here `length` and `uppercase()` are called on the **non-null** `String`, and the result type is `String?`, completed by `?:` for the null case. ## Why with can't do this `with` is **not** an extension. Its signature is `fun <T, R> with(receiver: T, block: T.() -> R): R` — the object is an ordinary **argument**, not a receiver. There is no `x.` at the call site, so there is nothing for `?.` to guard. `with(x) { ... }` **always** runs the block. If `x` is nullable, the compiler forces you to deal with nullability inside the block, or you must guard manually: ```kotlin // won't compile cleanly if x is nullable and you call members: val r = with(nullableUser) { name } // name access needs x non-null ``` You'd instead write a manual check or use `?.run`. ## Idiomatic nullable configure-and-compute For a nullable object you want to **operate on and produce a value from**, `x?.run { ... } ?: default` is the canonical pattern. It: - short-circuits when null (no NPE), - gives the block a non-null `this`, - returns the computed value (or null), - pairs with `?:` for a fallback. ## Note: run with no receiver isn't nullable-aware The receiver-less `run { ... }` form has no object, so null safety doesn't apply — that's a different use (scoping locals). ## Caution `x?.run` short-circuits the **whole** block on null; you can't partially run it. If the block has side effects you needed regardless of null, restructure the code.

  • What is the static type of name?.run { length } when name is String??
    Int? — the block returns Int, but the safe call makes the whole expression nullable.
  • How would you make with handle a nullable object cleanly?
    You can't via with itself; you'd null-check first (if/let) or just switch to x?.run, which is the idiomatic choice for nullable receivers.

x?.run is a door with a lock: skip the room entirely if the key (x) is missing. with(x) has no lock on the way in.

saying these in an interview costs you the question

  • Claiming with(x) can be written with a safe call
  • Thinking x?.run still runs the block when x is null
  • Forgetting the result type becomes nullable after ?.run
  • Believing the receiver-less run { } participates in null safety

context