Beyond `return@label`, how are labels (`this@label`) used to disambiguate receivers in nested lambdas with receivers, and how does this relate to return labels?
answer
- Same labels drive return@ and this@
- this@label picks an outer receiver
- Inner receiver shadows outer in nested blocks
- this@ClassName reaches enclosing instance
- @DslMarker restricts implicit outer receivers
basics
~10 sThe same @label names that target return@label also qualify this@label. In nested blocks with receivers (like apply inside apply), this@outerLabel lets you reach an outer receiver instead of the innermost one.
solid answer
~40 sKotlin's label grammar serves two purposes that share the same names. `return@forEach` returns from a labelled lambda; `this@forEach` (or `this@ClassName`) selects which **receiver** you mean when several are in scope. In lambdas with receivers — `apply`, `run`, `with`, or any `T.() -> R` — the receiver is the implicit `this`. When such lambdas nest, the innermost `this` shadows the outer ones. The implicit label (function name) or an explicit `name@` lets you write `this@apply` / `this@outer` to reach the enclosing receiver, and `this@MyClass` reaches the surrounding class instance. So return labels and qualified-`this` are two faces of the same labeling mechanism: one controls control flow, the other resolves the receiver. This is central to writing type-safe Kotlin DSLs, often combined with `@DslMarker` to restrict implicit outer-receiver access.
code
kotlin · 12 linesclass Outer { val tag = "outer" }
class Inner { val tag = "inner" }
fun Outer.build(block: Outer.() -> Unit) = block()
fun Inner.use(block: Inner.() -> Unit) = block()
Outer().build outer@ {
Inner().use {
println(this.tag) // inner (nearest receiver)
println(this@outer.tag) // outer (qualified this via label)
}
}go deeper
May only know return@label; unaware that labels also qualify this.
Understands this@ClassName to reach the enclosing instance from a lambda.
Resolves nested receiver shadowing with this@label and ties it to return labels.
Designs type-safe DSLs using receiver labels plus @DslMarker to control scope access deliberately.
## One label grammar, two uses Kotlin reuses the `@label` name in two places: - **`return@label`** — non-/local return targeting a specific lambda. - **`this@label`** — a *qualified this* that picks one receiver when several are in scope. Both draw on the **implicit label** (the called function's name) or an **explicit `name@`** label. ## Lambdas with receivers A *lambda with receiver* has type `T.() -> R`; inside it, the receiver `T` is the implicit `this`. Scope functions `apply`, `run`, and `with` use this form: ```kotlin class Builder { var name = ""; val tags = mutableListOf<String>() } Builder().apply { name = "root" // 'this' is the Builder this.tags.add(name) } ``` ## Nesting shadows the receiver When receiver lambdas nest, the **innermost `this` shadows** outer receivers: ```kotlin class Html { fun body(block: Body.() -> Unit) { Body().block() } } class Body { fun p(block: P.() -> Unit) {} ; val bodyId = "b" } class P { val pId = "p" } Html().body bodyLabel@ { p { // 'this' here is P; to reach Body use the outer label: val outerId = [email protected] } } ``` Without a label, `this` is the nearest receiver (`P`). `this@bodyLabel` (explicit) or `this@body` (implicit, function name) reaches the enclosing `Body`. ## Reaching the surrounding class Inside any lambda you can reach the enclosing class instance with `this@ClassName`: ```kotlin class Service { val id = 1 fun run() = listOf(1).forEach { println([email protected]) // the Service instance } } ``` ## Relationship to return labels The mechanisms are parallel: - `return@body` → control flow: leave the `body` lambda. - `this@body` → data access: use the `body` lambda's receiver. Both are resolved by the same label, so an explicit `name@` simultaneously names the return target and the receiver qualifier. ## DSLs and `@DslMarker` This receiver resolution underpins type-safe builder DSLs. `@DslMarker` annotations restrict accidental access to *outer* implicit receivers, forcing an explicit `this@outer` when you genuinely need the enclosing scope — improving safety in nested DSLs. ## Key terms - **Lambda with receiver**: `T.() -> R`; `this` is the receiver. - **Qualified this `this@label`**: selects a specific receiver/class instance. - **Shadowing**: inner receiver hides outer receivers. - **`@DslMarker`**: restricts implicit outer-receiver access in DSLs.
- If `return@forEach` and `this@forEach` use the same label, how do you keep two nested receivers straight?Give each lambda an explicit label (`outer@`, `inner@`); then `this@outer`/`this@inner` and `return@outer`/`return@inner` unambiguously select the receiver or return target you mean.
- What does `@DslMarker` add on top of qualified `this`?It makes implicit access to an outer DSL receiver a compile error, so you must explicitly write `this@outer` to reach it — preventing accidental cross-scope calls in nested builders.
saying these in an interview costs you the question
- Thinking labels only apply to returns, not to `this`
- Believing the outer receiver is reachable implicitly when shadowed
- Confusing `this@ClassName` with `this::class`
- Not knowing nested receiver lambdas shadow `this`
- Unaware `@DslMarker` interacts with implicit receiver access