
@kotlin.internal.InlineOnly public inline fun CharSequence?.isNullOrBlank(): Boolean { contract { returns(false) implies (this@isNullOrBlank != null) } return this == null || this.isBlank() } public inline fun measureTimeMillis(block: () -> Unit): Long { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start }
contract function, which for now can only be used as the start of the body of a top-level function. The contract function starts the DSL builder. This is a peculiar function because it is an inline function with an empty body.@ContractsDsl @ExperimentalContracts @InlineOnly @SinceKotlin("1.3") @Suppress("UNUSED_PARAMETER") inline fun contract(builder: ContractBuilder.() -> Unit) {}
isNullOrBlank contract specifies when the function returns false, thus the Kotlin compiler can assume that the receiver is not null. This information is used for smart-casting. The contract of measureTimeMillis specifies that the block function will be called in place exactly once. Let’s see what this means and how exactly we can specify a contract.// C++ int mul(int x, int y) [[expects: x > 0]] [[expects: y > 0]] [[ensures audit res: res > 0]]{ return x * y; }
require and check functions to specify expectations on arguments and states.fun mul(x: Int, y: Int): Int { require(x > 0) require(y > 0) return x * y }
callsInPlace to guarantee that a parameter with a functional type is called in place during function execution. Using a value from the InvocationKind enum, this structure can also be used to specify how many times this function is executed.@kotlin.internal.InlineOnly public inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
EXACTLY_ONCE- specifies that this function will be called exactly once.AT_MOST_ONCE- specifies that this function will be called at most once.AT_LEAST_ONCE- specifies that this function will be called at least once.UNKNOWN- does not specify the number of function invocations.
callsInPlace is information to the compiler, which can use this information in a range of situations. For instance, a read-only variable cannot be reassigned, but it can be initialized separately from the definition.fun main() { val i: Int i = 42 println(i) // 42 }
fun main() { val i: Int run { i = 42 } println(i) // 42 }
result variable, which is defined in the forceExecutionTimeMillis function and initialized in the lambda expression in the measureTimeMillis function. This is possible only because the measureTimeMillis parameter block is defined to be called in place exactly once.suspend fun <T, R> forceExecutionTimeMillis( timeMillis: Long, block: () -> R ): R { val result: R val timeTaken = measureTimeMillis { result = block() } val timeLeft = timeMillis - timeTaken delay(timeLeft) return result } @OptIn(ExperimentalContracts::class) inline fun measureTimeMillis(block: () -> Unit): Long { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start }
Contracts are still an experimental feature. To define contracts in your own functions, you need to useOptInannotation withExperimentalContracts. It is extremely unlikely Kotlin will drop this functionality, but its API might change.
EXACTLY_ONCE invocation kind is the most popular because it offers the most advantages. With this invocation kind, read-only properties can only be initialized in blocks. Read-write properties can also be initialized and re-initialized in blocks whose invocation kind is AT_LEAST_ONCE.@OptIn(ExperimentalContracts::class) fun checkTextEverySecond(callback: (String) -> Unit) { contract { callsInPlace(callback, InvocationKind.AT_LEAST_ONCE) } val task = object : TimerTask() { override fun run() { callback(getCurrentText()) } } task.run() Timer().schedule(task, 1000, 1000) } fun main() { var text: String checkTextEverySecond { text = it } println(text) }
In the above code, only the first call ofcallbackis called in place, so this contract is not completely correct.
callsInPlace function is when a statement inside a lambda is terminal for function execution (declares the Nothing result type[^04_1]), therefore everything after it is unreachable. Thanks to the contract, this might include statements outside this lambda.fun main() { run { println("A") return println("B") // unreachable } println("C") // unreachable }
fun makeDialog(): Dialog { DialogBuilder().apply { title = "Alert" setPositiveButton("OK") { /*...*/ } setNegativeButton("Cancel") { /*...*/ } return create() } } fun readFirstLine(): String? = File("XYZ") .useLines { return it.firstOrNull() }
returns function and the infix implies function to imply some value-type implications based on the result of the function. The compiler uses this feature for smart-casting. Consider the isNullOrEmpty function, which returns true if the receiver collection is null or empty. Its contract states that if this function returns false, the compiler can infer that the receiver is not null.inline fun <T> Collection<T>?.isNullOrEmpty(): Boolean { contract { returns(false) implies (this@isNullOrEmpty != null) } return this == null || this.isEmpty() } fun printEachLine(list: List<String>?) { if (!list.isNullOrEmpty()) { for (e in list) { //list smart-casted to List<String> println(e) } } }
Loading because startedLoading returns true.@OptIn(ExperimentalContracts::class) fun VideoState.startedLoading(): Boolean { contract { returns(true) implies (this@startedLoading is Loading) } return this is Loading && this.progress > 0 }
returns function can only use true, false, and null as arguments. The implication must be either a parameter (or receiver) that is of some type or is not null.measureCoroutineTimedValue needs to return the measured time as well as the value which is calculated during its execution. To measure time, it uses measureCoroutineDuration, which returns Duration. To store the result of the body, it needs to define a variable. The body of measureCoroutineTimedValue only works because measureCoroutineDuration is defined in its callsInPlace contractwith
InvocationKind.EXACTLY_ONCE.@OptIn(ExperimentalContracts::class) suspend fun measureCoroutineDuration( body: suspend () -> Unit ): Duration { contract { callsInPlace(body, InvocationKind.EXACTLY_ONCE) } val dispatcher = coroutineContext[ContinuationInterceptor] return if (dispatcher is TestDispatcher) { val before = dispatcher.scheduler.currentTime body() val after = dispatcher.scheduler.currentTime after - before } else { measureTimeMillis { body() } }.milliseconds } @OptIn(ExperimentalContracts::class) suspend fun <T> measureCoroutineTimedValue( body: suspend () -> T ): TimedValue<T> { contract { callsInPlace(body, InvocationKind.EXACTLY_ONCE) } var value: T val duration = measureCoroutineDuration { value = body() } return TimedValue(value, duration) }
run, let, also, use, measureTime, isNullOrBlank, and many more; this makes their usage more elastic and supports better smart-casting. We rarely define contracts ourselves, but it’s good to know about them and what they offer.[^04_1]: For details, see The beauty of the Kotlin type system chapter in Kotlin Essentials.
[^04_2]: This is known as Design by contract, the advantage of which is that it frees a function from having to handle cases outside of the precondition. Bertrand Meyer coined this term in connection with his design of the Eiffel programming language.
