Контракты contracts функции
Контракты в Kotlin — это мощный инструмент, позволяющий разработчику предоставлять компилятору дополнительную информацию о поведении функций. Эта информация помогает компилятору лучше понимать логику программы и, как следствие, производить более точную проверку типов, а также улучшать оптимизацию кода.
Контракт — это декларация, описывающая гарантии, которые предоставляет функция. Например, контракт может сказать компилятору: "Если функция возвращает false
, то переданный аргумент точно не равен null
".
Контракты создаются с использованием специального DSL (domain-specific language) из пакета kotlin.contracts
. Этот DSL предоставляет несколько ключевых выражений, таких как returns
, implies
, и callsInPlace
.
returns
:
returns
описывает условие возвращаемого значения функции.returns(true)
означает, что функция возвращает true
в определенных условиях.implies
:
implies
используется для связывания результата выполнения функции с состоянием какого-либо выражения.returns(false) implies (x != null)
означает, что если функция возвращает false
, то x
не null
.callsInPlace
:
callsInPlace
указывает, как функция передает выполнение лямбды (или другой функции) внутри себя.callsInPlace(block, InvocationKind.EXACTLY_ONCE)
означает, что переданная лямбда-функция block
будет вызвана ровно один раз.inline
функциях, так как это требование для их работы в Kotlin.null
и оптимизация типаРассмотрим функцию, которая проверяет, является ли строка null
или пустой. Компилятор должен после вызова этой функции знать, что строка не null
, если функция вернула false
.
import kotlin.contracts.contract
fun String?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}
return this == null || this.isEmpty()
}
fun example() {
val str: String? = "Hello"
if (str.isNullOrEmpty()) {
println("String is null or empty")
return
}
// Здесь компилятор знает, что str не null, так как str.isNullOrEmpty() вернул false
println(str.length) // Безопасно использовать str, т.к. она не null
}
В этом примере функция isNullOrEmpty
возвращает true
, если строка null
или пустая. Контракт функции указывает, что если она вернула false
, то строка не может быть null
. Благодаря этому компилятор знает, что после проверки if (str.isNullOrEmpty())
, если условие не сработало, переменная str
не будет null
, и с ней можно работать как с обычной String
.
callsInPlace
Рассмотрим случай, когда вы хотите гарантировать, что переданная лямбда-функция будет вызвана определенное количество раз.
import kotlin.contracts.contract
import kotlin.contracts.InvocationKind
inline fun executeExactlyOnce(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
fun example() {
val x: Int
executeExactlyOnce {
x = 42
}
// Компилятор знает, что x инициализирован, так как блок был вызван ровно один раз
println(x) // Выводит: 42
}
В этом примере функция executeExactlyOnce
имеет контракт, который говорит компилятору, что переданная лямбда будет вызвана ровно один раз (InvocationKind.EXACTLY_ONCE
). Это позволяет компилятору понять, что переменная x
, инициализированная внутри лямбды, будет точно инициализирована после вызова executeExactlyOnce
.
Представим ситуацию, что нам надо выполнять проверку над Int
, и если она не null избегать лишних проверок. В этом нам поможет следующая конструкция
@OptIn(ExperimentalContracts::class)
fun Int?.izZeroOrNull(): Boolean {
contract {
returns(false) implies (this@izZeroOrNull != null)
}
return this == null || this == 0
}
Используя ее, компилятор точно будет знать что число не null
и позволит избегать добавления лишних проверок if