KotlinMiddleTechnical
Что такое contracts в Kotlin и как они помогают smart casts?
Contracts — API компилятора Kotlin, позволяющий функции сообщать гарантии о своём поведении (например, лямбда вызывается ровно один раз), что расширяет область применения smart cast.
Что такое contracts в Kotlin
Contracts (контракты) — экспериментальный механизм (@OptIn(ExperimentalContracts::class)), позволяющий разработчику явно сообщить компилятору о гарантиях выполнения функции. Компилятор использует эти сведения для уточнения анализа потока данных (flow analysis), в первую очередь для smart cast.
Контракты объявляются внутри блока contract { … } в начале тела функции с помощью DSL из пакета kotlin.contracts.
Два вида эффектов
- callsInPlace — сообщает, сколько раз будет вызвана переданная лямбда:
EXACTLY_ONCE,AT_LEAST_ONCE,AT_MOST_ONCE,UNKNOWN. - returns / returnsNotNull + implies — сообщает, что если функция вернула определённое значение (или вернула вообще), то выполняется некое условие на типы аргументов.
Пример: callsInPlace и инициализация val
import kotlin.contracts.*
@OptIn(ExperimentalContracts::class)
inline fun <T> myRun(block: () -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
fun demo() {
val x: Int
myRun {
x = 42 // компилятор знает: блок вызван ровно раз,
} // поэтому val считается определённой
println(x) // без контракта — ошибка "val must be initialized"
}
Пример: returns implies и smart cast
import kotlin.contracts.*
@OptIn(ExperimentalContracts::class)
fun requireNotEmpty(s: String?) {
contract {
returns() implies (s != null && s.isNotEmpty())
}
if (s.isNullOrEmpty()) throw IllegalArgumentException("empty")
}
fun greet(name: String?) {
requireNotEmpty(name)
println(name.length) // smart cast: name гарантированно не null
}
Стандартные функции, использующие contracts
let,run,apply,also,with—callsInPlace(block, EXACTLY_ONCE)check,require,checkNotNull,requireNotNull—returns() implies (value != null)
Ограничения
- API помечен как
@ExperimentalContracts— может измениться в будущих версиях. - Контракт верит разработчику: компилятор не проверяет, что указанный
InvocationKindдействительно соблюдается в теле функции. - Работает только для
inline-функций (дляcallsInPlace) или функций, принимающих функциональный тип.
Подводные камни
- Неверный
InvocationKind(например,EXACTLY_ONCEпри реальных нескольких вызовах) — компилятор доверяет контракту, но в рантайме поведение неопределено. - Контракты не работают для не-
inline-функций сcallsInPlace— получите ошибку компиляции. @ExperimentalContractsтребует явного@OptInвезде, иначе предупреждение превратится в ошибку при строгом режиме.- IDE (IDEA) показывает smart cast, опираясь на контракт, но при некорректном контракте это ложная уверенность.
- Блок
contract { }должен быть первым выражением в теле функции — иначе ошибка компиляции. - Контракты не наследуются и не переопределяются в override-методах.
returns(false) implies …иreturns(true) implies …нельзя комбинировать в одномcontract-блоке в текущей версии.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.