Igor Bubelov
About Blog Notes Photos

Kotlin 1.3 Features: Contracts

Programming · Kotlin · Dec 3, 2018

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 they can be used to express programmer’s intents in situations where compiler may have some doubts and refuse to do its job until there is no ambiguity.

Illustration

Example

Let’s look at the following code:

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 smart-cast. Let’s try to modify our example in order to achieve that:

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:

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’s 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:

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 smart-casted 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 an application codebase.