Kotlin 1.3 Features: Contracts

The Kotlin compiler is pretty smart when it comes to static analysis but there are still cases when the code looks perfectly obvious to a programmer but it’s very difficult to understand for the compiler. Contracts were introduced in Kotlin 1.3 and their sole purpose is to improve the static analysis by letting programmers provide an additional information on what to expect from each particular function.

Example

Let’s look at the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fun main() {
    signIn("qwerty")
}

fun signIn(password: String?) {
    if (validatePassword(password)) {
        println("Password starts with ${password[0]}")
        // Sign in
    }
}

fun validatePassword(password: String?): Boolean {
    return password != null && password.length > 0
}

This code looks safe in the eyes of a programmer but the Kotlin compiler will give us an error if we try to run this code. The reason is: if your nullability check is in the other function, the Kotlin compiler won’t figure it out by itself so the password will be considered nullable even if it was already checked during the invocation of one of the previously called functions.

Solution

Since Kotlin 1.3, we have a way to express the relationship between function arguments and function outcomes in a way that gives the compiler enough confidence to do the smartcast. Let’s try to modify our example in order to achieve that:

1
2
3
4
5
6
7
fun validatePassword(password: String?): Boolean {
    contract {
        returns(true) implies (password != null)
    }

    return password != null && password.length > 0
}

Now the compiler has a little more information on what we’re doing so it won’t complain about the possible nullability of the password variable given that we called the validatePassword method and it returned true.

Another Example

In this example we’re going to try to initialize a value inside the block of code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fun main() {
    val result: Int

    executeCode {
        result = 1
    }

    println(result)
}

inline fun executeCode(block: () -> Unit) {
    block()
}

This code looks innocent in the eyes of a programmer but the Kotlin compiler won’t let us run it. Let see what it says:

  • Captured values initialization is forbidden due to possible reassignment
  • Variable ‘result’ must be initialized

It seems like the compiler doesn’t like the fact that we moved the value initialization logic to another block of code. Are those concerns justified? Well, yes! What if we try to call the block() many times or just ignore it and have no calls at all, leaving the value uninitialized? That’s exactly the situation the compiler tries to avoid so it raises an error every time it meets such as ambiguity.

Solution

Luckily for us, there is a special type of contract that lets us give the compiler more information on how many times we’re going to call a certain block of code. Let’s modify our executeCode method:

1
2
3
4
5
6
7
inline fun executeCode(block: () -> Unit) {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }

    block()
}

Now we can compile and run our code without getting any errors.

Real World Usages

Contracts are already in use in the Kotlin Standard Library, here are a few examples:

  • synchronized() method
  • precondition functions
  • Standard Library functions that operate on blocks of code (also, apply, let, etc.)
  • string utility functions isNullOrBlank and isNullOrEmpty, so your strings could be smartcasted as null safe if one of those functions returned false
  • collection utility function isNullOrEmpty
  • Result extensions, for those who use the Result class that comes with the Kotlin Standard Library

Conclusion

Contracts can make your code more readable but you should be very careful with them because the compiler will not check that those contracts are actually correct: it’s going to trust you on that. Contracts are especially useful on the library level but they might also come in handy in the application codebase.