article banner

Kotlin Contracts

This is a chapter from the book Advanced Kotlin. You can find it on LeanPub or Amazon.

One of Kotlin’s most mysterious features is Kotlin Contracts. Most developers use them without even knowing how they work, or even how to define them. Kotlin Contracts make the Kotlin compiler smarter and allow us to do things that would otherwise be impossible. But before we see them in action, let’s start with a contract definition which can be found in some Kotlin stdlib functions.

@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 }

A Kotlin Contract is defined using the 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) {}

Inline function calls are replaced with the body of these functions0. If this body is empty, it means that such a function call is literally replaced with nothing, so it is gone. So, why might we want to call a function if its call is gone during code compilation? Kotlin Contracts are a way of communicating with the compiler; therefore, it’s good that they are replaced with nothing, otherwise they would only disturb and slow down our code after compilation. Inside Kotlin Contracts, we specify extra information that the compiler can utilize to improve the Kotlin programming experience. In the above example, the 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.

The meaning of a contract

In the world of programming, a contract is a set of expectations on an element, library, or service. By “contract”, we mean what is "promised" by the creators of this solution in documentation, comments, or by explicit code structures2. It’s no wonder that this name is also used to describe language structures that are used to specify expectations on some code elements. For instance, in C++ there is a structure known as a contract that is used to demand certain conditions for the execution of a function:

// C++
int mul(int x, int y)
 [[expects: x > 0]]       
 [[expects: y > 0]]
 [[ensures audit res: res > 0]]{
 return x * y;
}

In Kotlin, we use the 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 }

The problem is with expressing messages that are directed to the compiler. For this, we could use annotations (which was actually considered when Kotlin Contracts were being designed), but their expressiveness is much more limited than DSL. So, the creators of Kotlin defined a DSL that starts function definitions. Let's see what we can express using Kotlin Contracts.

How many times do we invoke a function from an argument?

As part of a contract, we can use 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() }

There are four possible kinds of invocation:

  • 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.

Execution of 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 }

When a functional parameter is specified to be called exactly once, a read-only property can be defined outside a lambda expression and initialized inside it.

fun main() { val i: Int run { i = 42 } println(i) // 42 }

Consider the 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 use OptIn annotation with ExperimentalContracts. It is extremely unlikely Kotlin will drop this functionality, but its API might change.

The 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 of callback is called in place, so this contract is not completely correct.

Another example of how the Kotlin Compiler uses the information specified by the callsInPlace function is when a statement inside a lambda is terminal for function execution (declares the Nothing result type1), 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 }

I've seen this used in many projects to return from a lambda expression.

fun makeDialog(): Dialog { DialogBuilder().apply { title = "Alert" setPositiveButton("OK") { /*...*/ } setNegativeButton("Cancel") { /*...*/ } return create() } } fun readFirstLine(): String? = File("XYZ") .useLines { return it.firstOrNull() }

Implications of the fact that a function has returned a value

Another kind of contract statement includes the 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) } } }

Here is a custom example in which the compiler can infer that the receiver is of type 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 }

Currently, the 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.

Using contracts in practice

Some important functions from Kotlin stdlib define their contract; we benefit from this as it makes the compiler smarter. Some developers don’t even notice how they use contracts and might be surprised to see how Kotlin allows something that would not be allowed in other languages, like smart-casting a variable in an unusual case. Having said that, you don’t even need to know how a contract works in order to benefit from it.

Defining contracts in our own top-level functions is extraordinarily rare, even for library creators. I’ve seen it done in some projects, but most projects don’t even have a single custom function with a specified contract. However, it’s good to understand contracts because they can be really helpful in some situations. A good example is presented in the article Slowing down your code with Coroutines by Jan Vladimir Mostert, which presents a technique to make some requests take a specific time. Consider the code below. The function 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 contract with 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) }

Summary

Kotlin Contracts let us specify information that is useful for the compiler. They help us define functions that are more convenient to use. Kotlin Contracts are defined in some Kotlin stdlib functions, like 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.

0:

I described inline functions in the Inline functions chapter in Functional Kotlin.

1:

For details, see The beauty of the Kotlin type system chapter in Kotlin Essentials.

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.