Understanding Kotlin contracts

Marcin Moskala
Kt. Academy
Published in
5 min readJan 7, 2019

--

Since Kotlin 1.3 we can enjoy new, mysterious feature — Kotlin contracts. From the usage perspective, they look just like a piece of code placed as the first statement on a function:

Though during compilation, this whole block vanishes. It is because contract builder function is an inline function with an empty body:

Looks crazy, but this is the desired behavior. Kotlin contracts are a way to communicate with the compiler, and they would only disturb and slow down our code after compilation otherwise. There are different kinds of information they can contain, and each of them improves Kotlin programming experience. Let’s see how.

This article is based on the update to Kt. Academy Kotlin workshop I introduced before. Since Kotlin 1.3 is already used, we teach newly introduced features just with a warning about version requirement.

What are contracts?

Traditionally contracts were just expressions that were required to be true. Like Kotlin require:

They were there to state some requirements that couldn’t be checked in any other way. For instance, in C++ templates type arguments could be anything. This is why contracts were there to say that only types with concrete methods or fields are allowed. So contracts were mainly information for a developer.

This kind of information we call a requirement in Kotlin. Kotlin contracts are there to communicate with the compiler, not with a developer. Kotlin team specified two kinds of messages that are meaningful for the compiler:

  • How many times do we invoke a function from an argument
  • Implications from the fact that function returned some value

Let’s see them in detail and what are the consequences of these contracts.

How many times do we invoke a function from an argument

We state how many times is a function called by callsInPlace block and InvocationKind:

There are four possible invocation kinds:

  • AT_MOST_ONCE
  • AT_LEAST_ONCE
  • EXACTLY_ONCE
  • UNKNOWN

They all allow a different kind of behaviours. First of all, when we know that code in a lambda expression is invoked exactly once, we know that we can initialize variable there:

It does not work for any other invocation kind because they either do not guarantee that variable will be initialized (AT_MOST_ONCE and UNKNOWN) or they might try to reinitialize val (AT_LEAST_ONCE)

If we want to initialize/set var, we can do it both when lambda is initialized EXACTLY_ONCE and AT_LEAST_ONCE:

Warnings are also supported. The code in a block that is invoked at least once can be treated as part of the function.

The point is that return is not necessary after block if it is used inside:

fun makeDialog(): Dialog {
DialogBuilder().apply {
title = "Alert"
setPositiveButton("OK", { /**/ })
setNegativeButton("Cancel", { /**/ })
return create()
}
}

In the end, this 3 capabilities for each invocation kind can be summarized in the following table:

Implications from the fact that function returned some value

A great example of this functionality is theCollection<T>?.isNullOrEmpty function. Traditionally we could use it to check if a collection is null or empty, but it wasn’t able to smart cast this variable. Now it is, thanks to Kotlin contracts. This is how function declaration looks like:

The contract states that if the function returns false, then the receiver is not null. Based on this information compiler smart casts variable if it knows that function returned false:

This feature can also be used to achieve smart casting for types:

Now only true/false return values are allowed, what satisfies most cases. Though maybe one day we will be able to communicate the implications of different kinds of results as well.

Is it all?

Yes, for now. I hope that Kotlin coroutines will progress and let us express more information about functions. For instance, if we could say that either one or another function will be called, we could cover more kinds of callbacks:

suspend fun getUserId(): Int? {
getUser(
onSuccess = { return it.id },
onError = { return null }
)
}

Also, more powerful effects inference might be useful. Though, for now, this is just wishful thinking.

Summary

Kotlin contracts introduced important improvements, especially for Standard Library functions. These improvements couldn’t be achieved in any other way but thanks to the cooperation between language and compiler. They gave us even smarter compiler, so better programming experience.

Click the 👏 to say “thanks!” and help others find this article.

Do you need a Kotlin workshop? Visit our website to see what we can do for you.

To be up-to-date with great news on Kt. Academy, subscribe to the newsletter, observe Twitter and follow us on medium.

--

--

Kt. Academy creator, co-author of Android Development with Kotlin, author of open-source libraries, community activist. http://marcinmoskala.com/