CodeBlanks Logo Icon CodeBlanks

Type-safe builders

Learn type-safe builders with @DslMarker.

What does @DslMarker do?

When writing a DSL, nested lambda blocks may accidentally use the receiver of an outer block. @DslMarker narrows that scope.

With @DslMarker annotation class MenuDsl you declare a marker. All receivers carrying the same marker cannot mix in nested blocks. So inside item { name = "Soup" } you cannot reach the outer menu block by mistake. This is a compile-time guard; no runtime cost. It appears in DSLs like html { ... } in the Kotlin standard library.

A builder DSL

To write a type-safe builder, you add the @MenuDsl marker to a class and give it an extension function.

Example: @MenuDsl class Menu { val items = mutableListOf<Item>(); fun item(block: Item.() -> Unit) { items.add(Item().apply(block)) } }. The outer entry is fun menu(block: Menu.() -> Unit): Menu = Menu().apply(block). This lets you write menu { item { name = "Soup" } }. Thanks to @DslMarker, the item block cannot accidentally touch the items list of the outer menu.

Practice Write the menu DSL

Define @DslMarker annotation class MenuDsl. Class Item has var name: String = "" and var price: Int = 0. Inside @MenuDsl class Menu add val items = mutableListOf<Item>() and fun item(block: Item.() -> Unit). Write the menu function and in main add two items, then write println(m.items.size).

Starter code

Main.kt
// Write @DslMarker annotation and Menu DSL

class Item {
    var name: String = ""
    var price: Int = 0
}

// Add the Menu class and menu function

fun main() {
    val m = menu {
        item { name = "Soup"; price = 30 }
        item { name = "Rice"; price = 45 }
    }
    // print the item count
}

Sample solution

Main.kt
@DslMarker
annotation class MenuDsl
class Item { var name: String = ""; var price: Int = 0 }
@MenuDsl
class Menu {
    val items = mutableListOf<Item>()
    fun item(block: Item.() -> Unit) { items.add(Item().apply(block)) }
}
fun menu(block: Menu.() -> Unit): Menu = Menu().apply(block)
fun main() {
    val m = menu {
        item { name = "Soup"; price = 30 }
        item { name = "Rice"; price = 45 }
    }
    println(m.items.size)
}

Expected output

2

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Define the @DslMarker annotation

Question
____
annotation class MenuDsl
Answer
@DslMarker
annotation class MenuDsl

Explanation: @DslMarker creates a DSL marker class.

2 What does @DslMarker prevent?

  • A runtime error in a lambda
  • Compiling inline functions
  • Defining an annotation class
  • Wrong use of an outer receiver in nested DSL blocks Correct answer

Explanation: @DslMarker stops accidental access to the outer receiver from inner blocks.

3 Match builder parts

Term Meaning
@DslMarker Marker definition
annotation class Marker class
@MenuDsl Receiver marker
block: Item.() -> Unit Lambda receiver type

All lessons in Expert