After @DslMarker hides an outer receiver inside a nested block, how can you still legitimately call a member of that outer receiver?
answer
- Hidden, not gone — instance still in scope
- [email protected]()
- Implicit label = builder function name
- Or capture: val outer = this
- Explicit call signals intent
basics
~10 sUse a labeled this, like [email protected](). The label names the enclosing block, giving you explicit access to the receiver that the marker hid from implicit calls.
solid answer
~40 s@DslMarker only blocks the *implicit* (unqualified) use of the outer receiver; the receiver instance is still there. You reach it with a **qualified this** using the implicit label of the enclosing lambda: if you have `html { body { ... } }`, inside `body` you write `[email protected]()`. The label `html` comes from the name of the function whose lambda established that receiver (the implicit label equals the function name). You can also assign the outer receiver to a named variable before entering the inner block (`val outer = this`) and use `outer.someMember()`. Both are explicit, so they signal deliberate cross-scope access rather than the accidental call the marker is meant to prevent. This keeps the safety net while still allowing the rare legitimate case.
code
kotlin · 10 lines@DslMarker annotation class HtmlDsl
@HtmlDsl class HtmlBuilder { fun title(t: String) {} ; fun body(b: BodyBuilder.() -> Unit) {} }
@HtmlDsl class BodyBuilder
fun html(b: HtmlBuilder.() -> Unit) = HtmlBuilder().apply(b)
fun page() = html {
body {
this@html.title("Hi") // explicit, allowed despite the marker
}
}go deeper
May know that 'this@something' exists but be unsure how the label is derived.
Correctly uses this@html and knows the label is the enclosing builder function's name.
Also mentions capturing the receiver into a variable and explains why explicit access is acceptable under the marker's intent.
Discusses when exposing the outer receiver intentionally is good DSL design vs. a smell, and how custom labels affect readability.
## What @DslMarker actually blocks `@DslMarker` removes the outer same-marked receiver from **implicit receiver resolution** only. The instance is not destroyed and not out of scope — you simply can't call its members without saying which receiver you mean. ## Escape 1 — qualified `this` with an implicit label Every lambda-with-receiver gets an **implicit label** equal to the name of the function it's passed to. So in: ```kotlin html { // implicit label: this@html body { // implicit label: this@body [email protected]("Hello") // reach the hidden outer receiver } } ``` `this@html` is the `HtmlBuilder`; `this@body` (or plain `this`) is the `BodyBuilder`. The qualified form is explicit, so the compiler allows it even though `title` belongs to the marked outer receiver. ## Escape 2 — capture into a variable ```kotlin html { val outer = this // capture HtmlBuilder body { outer.title("Hello") // explicit reference, no marker conflict } } ``` This works because `outer` is an ordinary local variable, not an implicit receiver, so the shadowing rule doesn't apply to it. ## Why both are fine The whole point of `@DslMarker` is to stop **accidental** cross-scope calls. An explicit `this@html.` or `outer.` is a clear, intentional signal — exactly what you want for the rare legitimate case where an inner block must touch the outer builder. ## Common pitfalls - Using a custom lambda label (`html label@ { ... }`) changes the label name; then you'd write `this@label`. - Plain `this` inside the inner block refers to the **nearest** receiver, not the outer one — a frequent mistake. - If you renamed nothing, the implicit label is just the builder function's name. ## Summary Hidden ≠ gone. `[email protected]()` or capturing `val outer = this` both restore explicit access while preserving the safety the marker provides.
- What does the implicit label this@html refer to?The receiver established by the lambda passed to the function named html — i.e. the HtmlBuilder instance for that enclosing block.
- Does capturing val outer = this defeat the purpose of @DslMarker?Not really — it's still explicit and intentional. The marker only guards against accidental implicit calls; deliberate captured references are the sanctioned escape.
The marker locks the door to the outer room's shelf; the labeled this is the key you must consciously turn.
saying these in an interview costs you the question
- Saying the outer receiver is gone and cannot be accessed at all
- Thinking plain this inside the inner block reaches the outer receiver
- Not knowing the implicit label equals the function name
- Claiming you must remove the marker to call the outer member
- Confusing this@label with return@label