Привет, Хабр!
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 не только в проде, но и в тестовой инфраструктуре, посмотрите курс 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)

emerald_isle
30.12.2025 09:31Котлинеры переизобрели implicit parameters из Scala?

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

emerald_isle
30.12.2025 09:31На самом деле даже это только половина правды. В Scala 3 implicit синтаксис убрали, переосмыслили и сделали более понятную альтернативу. Разделили разные сценарии использования, так сказать, на разный синтаксис, это сильно упрощает чтение и понимание кода.
Kotlin плетётся где-то в хвосте, писать на нём вовсе не так приятно, но при этом он куда более популярен. Загадка.

UbuRus
30.12.2025 09:31Kotlin плетётся где-то в хвосте, писать на нём вовсе не так приятно, но при этом он куда более популярен. Загадка.
Каком хвосте? Wanna be functional language? Ну так у Kotlin в приоритете как раз таки фичи которые позволяют ему быть практичным, безопасным и популярным, а не фп-чистеньким. Видимо вот тут кроется разгадка
panzerfaust
Какая последовательность мыслей стояла за этим? Типа не хотим богомерзкий ООП с DI, хотим православный ФП? Но зависимости в реальных задачах все равно нужны - поэтому давайте навертим вот этой вермишели.
Уже в видел в коде нашего котлин-энтузиаста. Не понравилось. Не решает никакие проблемы, зато добавляет необходимость знать о новой фиче.
UbuRus
Это вообще не замена DI, разве что если передавать что-то типо
ApplicationContextи оттуда сервисы доставать в каждом методе. Почему? Да потому что context "окрашивает" функцию, теперь чтобы вызватьoutputMessageнужно коллеру знать проUserServiceи положить его в контекст. Т.е это не DIЧто же это?
Классная штука для DSL - тут прямо очень много возможностей открывается
Для меня основной юзкейс это компайл-тайм-тред-сейф замена ThreadLocal
Еще вероятно кейсы которые мне не доводилось использовать
Замена
ThreadLocalсамая приятная, у меня контроллеры завернуты вcontext(_: UserContext)и черезHandlerMethodArgumentResolverUserContextпостроенный из запроса передается в контроллер, дальше все функции по стеку определяют что им нуженcontext(_: UserContext)и получаем секьюрити без багов, даже с учетом что мы используем и корутины, и реактор и акторы и у нас библиотека которая используется и со спрингом и без спринга. В общем супер производительный, пуленепробиваемый способ делать секьюрити и транзакции в нашем случае.Я сейчас развиваю контексты в сторону использования их со всякими @Cached/@Retry, чтобы везде отказаться от неявной передачи/хранения стейта, на явные контексты.
Так что фича очень удобная для написания быстрого и безопасного кода. Особенно актуально в эру ИИ
panzerfaust
Штош, подождем наработки best practices