@DslMarker: предотвращение утечки получателя
Используйте аннотации @DslMarker, чтобы проверять правила вложенности DSL во время компиляции.
«@DslMarker: предотвращение утечки получателя» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.
Проблема утечки получателя
Во вложенных DSL внутренняя лямбда функции-построителя может случайно получить доступ к членам внешнего получателя. Например, внутри блока td { } можно вызвать tr { } из внешней области видимости таблицы — что почти никогда не предполагается.
Как выглядит утечка
Без @DslMarker компилятор разрешает вызывать функции из внешней области видимости внутри любой лямбды, что приводит к запутанному и подверженному ошибкам использованию DSL:
// Without @DslMarker — this compiles but is wrong:
html {
body {
table {
tr { // outer scope leaks in
tr { } // calling tr inside tr — nonsensical
}
}
}
}Введение @DslMarker
@DslMarker — это метааннотация, которую применяют к собственному классу аннотации. У любого класса-построителя, помеченного вашей аннотацией DSL, неявный получатель будет скрыт во вложенных лямбдах, использующих ту же аннотацию DSL.
@DslMarker
annotation class HtmlDsl
@HtmlDsl
class Table
@HtmlDsl
class TrКак работает скрытие неявного получателя
Когда две вложенные лямбды имеют получателей, помеченных одной и той же аннотацией @DslMarker, внешний получатель скрывается внутри внутренней лямбды. Прямой доступ к нему вызывает ошибку компиляции.
@DslMarker
annotation class HtmlDsl
@HtmlDsl class Html
@HtmlDsl class Body
@HtmlDsl class DivПолный пример с @DslMarker
Объявите аннотацию, пометьте классы-построители, и тогда вложенные вызовы методов из внешней области видимости будут обнаруживаться во время компиляции:
@DslMarker
annotation class MyCfgDsl
@MyCfgDsl
class ServerBuilder {
var port: Int = 8080
fun database(block: DbBuilder.() -> Unit) { /*...*/ }
}
@MyCfgDsl
class DbBuilder {
var url: String = ""
// Cannot call ServerBuilder.port here — compiler error
}Обход ограничения с помощью this@
Если вам действительно нужно обратиться к внешнему получателю из внутренней лямбды, используйте ссылку с меткой this@OuterClass. Такой доступ явный и намеренный, в отличие от случайной утечки.
fun ServerBuilder.database(block: DbBuilder.() -> Unit) {
val db = DbBuilder()
db.block()
val serverPort = this@ServerBuilder.port // explicit outer access
}@DslMarker с несколькими DSL
Разные DSL должны использовать разные аннотации @DslMarker. Два не связанных между собой DSL с разными маркерами не мешают друг другу даже при вложении.
@DslMarker annotation class HtmlDsl
@DslMarker annotation class GradleDsl
// HtmlDsl and GradleDsl receivers do not hide each otherПрименение к функциям-построителям и получателям
Примените аннотацию DSL к самому классу-построителю. Аннотация распространяется на любой параметр лямбды, тип получателя которого — этот класс. Дополнительная аннотация отдельных функций не требуется.
Поддержка IDE
IntelliJ IDEA и Android Studio сразу выделяют доступ к внешнему получателю как ошибку, если используется @DslMarker. Это обеспечивает обратную связь в реальном времени при создании или использовании DSL.
Проектирование с учётом безопасности
Хорошее проектирование DSL связывает каждый класс-построитель с @DslMarker. Небольшие накладные расходы на аннотацию значительно повышают удобство DSL и предотвращают целые категории ошибок пользователей DSL.
Использование в стандартной библиотеке
Собственная аннотация @DslMarker Kotlin используется в kotlinx.html, DSL маршрутизации Ktor, скрипте Kotlin для Gradle и функциях-построителях buildMap/buildList. Это фундаментальный инструмент любого серьёзного набора средств для создания DSL.
Быстрая проверка
Что предотвращает @DslMarker?
Итоги: @DslMarker
Основные выводы:
- Без
@DslMarkerвнешние получатели проникают во внутренние лямбды DSL - Определите класс аннотации с метааннотацией и примените его ко всем классам-построителям в вашем DSL
- Вложенные лямбды с одним и тем же маркером не могут случайно вызывать члены внешней области видимости
- Используйте
this@OuterClass, когда действительно нужен доступ к внешней области видимости
Часто задаваемые вопросы
Урок «@DslMarker: предотвращение утечки получателя» бесплатный?
Да — полный текст урока «@DslMarker: предотвращение утечки получателя» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.
Чему я научусь в уроке «@DslMarker: предотвращение утечки получателя»?
Используйте аннотации @DslMarker, чтобы проверять правила вложенности DSL во время компиляции. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Kotlin Academy?
Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «@DslMarker: предотвращение утечки получателя»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Kotlin Academy?
Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Лямбда с получателем: основа DSL
- @DslMarker: предотвращение утечки получателя
- Создание типобезопасного DSL для HTML/конфигурации
- Тестирование и развитие DSL без нарушения совместимости