How do you make two receivers available inside the same block using only standard scope functions, e.g. with(a) { b.apply { ... } }, and which receiver wins when both have a member with the same name?
answer
- with(a){ b.apply{} } stacks two receivers
- innermost receiver wins on name clash
- this@with reaches the outer receiver
- apply/also return receiver; with/run/let return result
- use also/let to make one object 'it' and dodge clashes
basics
~20 sNest scope functions: with(a) { b.apply { ... } }. Inside the inner block both a and b are reachable. If both define the same name, the innermost receiver (b) is used unless you qualify it.
solid answer
~40 sScope functions like with(a) { } and b.apply { } install a as the receiver of the outer lambda and b as the receiver of the inner lambda. Nesting them stacks both receivers so the inner block can call members of both a (outer) and b (inner) without qualification. Resolution is innermost-first: when a name exists on both, the nearest receiver (b) shadows the outer one (a). To reach the shadowed outer member you qualify with a labeled this: this@with, or restructure. apply/also return the receiver; with/run/let return the lambda result. Use also/let when you prefer it as a named parameter (it) instead of an implicit receiver, which avoids ambiguity entirely. This stacking is exactly how type-safe builder DSLs nest scopes.
code
kotlin · 10 linesdata class Server(val name: String) { fun log(m: String) = println("[$name] $m") }
class Request { var path: String = "/"; fun log(m: String) = println("req: $m") }
fun handle(server: Server, request: Request) = with(server) { // this = server
request.apply { // this = request
path = "/users"
log("inner") // Request.log (innermost)
this@with.log("outer") // Server.log via label
}
}go deeper
Can nest with/apply and knows it stacks both objects into scope.
Explains innermost-first resolution and uses this@label to disambiguate; picks apply vs run by return type.
Reaches for also/let to avoid clashes, reasons about readability of nested receivers, and knows when to flatten instead.
Weighs nested-scope DSL ergonomics vs context parameters, and sets team conventions for receiver nesting depth.
## The problem A lambda with receiver runs its body as if `this` were some object. Standard library **scope functions** install such receivers. The common ones: - `with(a) { ... }` — `a` is `this` inside; returns the lambda result. - `a.run { ... }` — like `with` but an extension; `a` is `this`; returns lambda result. - `a.apply { ... }` — `a` is `this`; returns **`a`** (good for configuring then returning). - `a.also { ... }` — `a` is **`it`** (a parameter, not a receiver); returns `a`. - `a.let { ... }` — `a` is **`it`**; returns lambda result. ## Stacking two receivers To have **two** receivers active at once, nest receiver-style scope functions: ```kotlin with(server) { // this == server (outer receiver) request.apply { // this == request (inner receiver) // both reachable here: path = "/users" // request.path (inner) log("handling") // server.log(...) (outer, unqualified) } } ``` Inside the inner block the compiler searches receivers **innermost-first**. `path` resolves on `request`; `log` is not on `request`, so it falls through to `server`. ## Shadowing & qualification If both receivers expose the same member name, the **innermost wins** and shadows the outer. To reach the outer one, use a **labeled `this`**: ```kotlin with(outer) { inner.apply { name // inner.name (shadows) [email protected] // outer.name (explicitly qualified) } } ``` The label after `this@` is the function name (`this@with`, `this@apply`) or an explicit lambda label (`outer@ { }` → `this@outer`). ## Avoiding ambiguity Use `also`/`let` for one of the objects so it becomes `it` (a normal parameter), not an implicit receiver — then there is no name clash: ```kotlin with(server) { request.also { req -> req.path = "/users" log("handling " + req.path) } } ``` ## Picking the right function - Return the object configured → `apply`/`also`. - Return a computed result → `with`/`run`/`let`. - Want implicit `this` → `with`/`run`/`apply`. - Want explicit `it` → `let`/`also`. This nesting is the manual way to get multiple contexts; the language-level alternative is **context parameters** (`context(...)`), covered separately.
- Why might you prefer also { it -> } over apply { } when nesting?also exposes the object as the named parameter it instead of an implicit receiver, so it can never clash with the outer receiver's members and the code reads more explicitly.
- Does with(a){ b.run { } } stack receivers the same way as b.apply { }?Yes for receiver stacking — run also makes b the this. The difference is the return value: run returns the lambda result, apply returns b.
Nested rooms: standing in the inner room you can still reach shelves in the outer room, but if both rooms have a 'door' the nearest one is what 'the door' means.
saying these in an interview costs you the question
- Thinks the outer receiver wins on a name clash
- Doesn't know this@with / labeled this to reach a shadowed receiver
- Confuses apply (returns receiver) with run/with (returns result)
- Believes also/let provide an implicit this rather than it
- Thinks you can only ever have one receiver in scope