Привет, Хабр!

Cегодня разберём экспериментальную фичу Kotlin 2.2 — контекстные параметры. C помощью контекстных параметров функции и свойства могут объявлять зависимости, которые неявно передаются при вызове.

Допустим, есть сервис логирования UserService, который нужно часто передавать во многие функции. Без контекстных параметров пришлось бы всюду писать fun outputMessage(users: UserService, msg: String). С параметрами контекста достаточно объявить функцию так:

interface UserService {
    fun log(message: String)
}

context(users: UserService)
fun outputMessage(message: String) {
    users.log("Log: $message")
}

context указывает, что функция outputMessage ожидает в своём окружении объект UserService. То есть при вызове компилятор неявно подставит нужный сервис из контекста.

Также параметры контекста можно применять и к свойствам. Например:

context(users: UserService)
val firstUser: String
    get() = users.findUserById(1)

Этот синтаксис делает firstUser ленивым свойством, которое берёт UserService из контекста и возвращает пользователя по ID.

Kotlin разрешает контекстные параметры по типу в месте вызова: достаточно положить объект нужного типа в контекст с помощью стандартных функций with/run/apply или специального блока context(obj) { ... }. Если в одном месте окажется несколько объектов с одинаковым типом, компилятор выдаст ошибку неоднозначности.

Но если вызвать функцию без необходимого контекста, Kotlin тоже выдаст ошибку компиляции, все context‑параметры обязаны быть обеспечены объектом нужного типа.

Можно использовать context(_ : Type), если нужен только тип в контексте, а не именованно к нему обращаться. Тогда внутри функции объект доступен, но по имени _ обращаться к нему нельзя.

На данный момент контекстные параметры всё ещё экспериментальны. Основные ограничения:

  • Конструкторы не могут иметь context‑параметров.

  • Свойства с context не могут иметь полей или начальных значений.

  • Свойства с context не могут использовать делегаты (by). То есть context может стоять только перед функцией или перед val/var без инициализатора.

Чтобы опробовать эту фичу в своём проекте, нужно включить флаг компилятора -Xcontext-parameters. Например, в Gradle‑файле можно добавить:

kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xcontext-parameters")
    }
}

Для наглядности ещё один пример: предположим, есть Logger и TransactionManager, которые часто используются вместе. С контекстными параметрами можно сделать так:

context(logger: Logger, tx: TransactionManager)
fun perform(data: Data) {
    logger.debug("Start processing $data")
    tx.run {
        // операции в транзакции
    }
    logger.debug("End")
}

fun main() {
    val data = Data()
    with(ConsoleLogger()) {
        with(TransactionManager()) {
            perform(data)  // Logger и TransactionManager предоставлены автоматически
        }
    }
}

Вызов функции perform внутри вложенных with блоков автоматически предоставляет и Logger, и TransactionManager: внутри функции сразу доступны оба контекста.

Без контекстных параметров пришлось бы прокидывать оба объекта во все вызовы. Причём если забыть обернуть вызов в необходимый context, компиляция не пройдёт — Kotlin потребует нужные объекты.

Подытожим: context‑параметры позволяют вынести общие зависимости (сервисы, менеджеры и тому подобное) из сигнатур функций, сделав код чище и короче. Они автоматически подставляются по типу, а сама механика упрощает внедрение зависимостей и дизайн DSL. Фича пока доступна через EAP, но обещано, что в следующих версиях Kotlin она будет стабилизирована.

Потянете курс Kotlin QA Engineer? Пройдите тест и узнаете
Потянете курс Kotlin QA Engineer? Пройдите тест и узнаете

Если вам интересно применить Kotlin не только в проде, но и в тестовой инфраструктуре, посмотрите курс Kotlin QA Engineer. Он про автотесты для UI, бизнес-логики и API на разных платформах, DI (Hilt) и Jetpack Compose, плюс встраивание тестов в CI/CD с упором на нагрузку, стиль и базовую безопасность.

Для знакомства с форматом обучения и экспертами приходите на бесплатные демо-уроки:

  • 15 января, 20:00. «Mutation Testing: как я узнал, что мои тесты с 95% coverage ничего не проверяют». Записаться

  • 21 января, 20:00. «Особенности Kotlin в UI и API тестировании». Записаться

Комментарии (9)


  1. panzerfaust
    30.12.2025 09:31

    Какая последовательность мыслей стояла за этим? Типа не хотим богомерзкий ООП с DI, хотим православный ФП? Но зависимости в реальных задачах все равно нужны - поэтому давайте навертим вот этой вермишели.

    Уже в видел в коде нашего котлин-энтузиаста. Не понравилось. Не решает никакие проблемы, зато добавляет необходимость знать о новой фиче.


    1. UbuRus
      30.12.2025 09:31

      Это вообще не замена DI, разве что если передавать что-то типо ApplicationContext и оттуда сервисы доставать в каждом методе. Почему? Да потому что context "окрашивает" функцию, теперь чтобы вызвать outputMessage нужно коллеру знать про UserService и положить его в контекст. Т.е это не DI

      Что же это?

      • Классная штука для DSL - тут прямо очень много возможностей открывается

      • Для меня основной юзкейс это компайл-тайм-тред-сейф замена ThreadLocal

      • Еще вероятно кейсы которые мне не доводилось использовать

      Замена ThreadLocal самая приятная, у меня контроллеры завернуты в context(_: UserContext) и через HandlerMethodArgumentResolver UserContext построенный из запроса передается в контроллер, дальше все функции по стеку определяют что им нужен context(_: UserContext) и получаем секьюрити без багов, даже с учетом что мы используем и корутины, и реактор и акторы и у нас библиотека которая используется и со спрингом и без спринга. В общем супер производительный, пуленепробиваемый способ делать секьюрити и транзакции в нашем случае.

      Я сейчас развиваю контексты в сторону использования их со всякими @Cached/@Retry, чтобы везде отказаться от неявной передачи/хранения стейта, на явные контексты.

      Так что фича очень удобная для написания быстрого и безопасного кода. Особенно актуально в эру ИИ


      1. panzerfaust
        30.12.2025 09:31

        Штош, подождем наработки best practices


  1. emerald_isle
    30.12.2025 09:31

    Котлинеры переизобрели implicit parameters из Scala?


    1. UbuRus
      30.12.2025 09:31

      В скале куда мощнее и интереснее. Тут это просто параметр который берется из скоупа вызова (класс-левел, топ-левел в резолве не участвует, только то что в функции самой) и имплиситно передается. В общем до скаловских имплиситов тут очень далеко


      1. NeoCode
        30.12.2025 09:31

        А что в Scala, хотя бы кратко? Интересно:)


      1. emerald_isle
        30.12.2025 09:31

        На самом деле даже это только половина правды. В Scala 3 implicit синтаксис убрали, переосмыслили и сделали более понятную альтернативу. Разделили разные сценарии использования, так сказать, на разный синтаксис, это сильно упрощает чтение и понимание кода.

        Kotlin плетётся где-то в хвосте, писать на нём вовсе не так приятно, но при этом он куда более популярен. Загадка.


        1. UbuRus
          30.12.2025 09:31

          Kotlin плетётся где-то в хвосте, писать на нём вовсе не так приятно, но при этом он куда более популярен. Загадка.

          Каком хвосте? Wanna be functional language? Ну так у Kotlin в приоритете как раз таки фичи которые позволяют ему быть практичным, безопасным и популярным, а не фп-чистеньким. Видимо вот тут кроется разгадка


  1. NeoCode
    30.12.2025 09:31

    Красивая фича