Kotlin Contracts

December 3, 2018  |  Programming  ·  Kotlin

Preface

The Kotlin compiler is pretty smart about static analysis, but there are still times when code looks perfectly obvious to a programmer, but is very hard for the compiler to figure out. Contracts were introduced in Kotlin 1.3 to solve exactly that. You can use them to tell the compiler your intent in situations where it can’t be sure, clearing up ambiguity so it can do its job.

Example

Consider this 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 right to a programmer, but the Kotlin compiler will throw an error. The reason is: Kotlin can’t track nullability checks across function boundaries. So even if password was validated in a called function, the compiler still sees it as nullable here.

Solution

Since Kotlin 1.3, we can explicitly define the relationship between a function’s inputs and its outcome so the compiler is confident enough to perform a smart cast. Let’s modify our example to do 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 about what we’re doing. Because we called validatePassword and it returned true, the compiler won’t complain about password being nullable here.

Another Example

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

fun main() {
    val result: Int

    executeCode {
        result = 1
    }

    println(result)
}

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

This code looks innocent to a programmer, but the Kotlin compiler won’t run it. Let’s see what it says:

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

Solution

Luckily, there’s a special type of contract that lets us tell the compiler exactly how many times a given block of code will be called. 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 Usage

Contracts are already used in the Kotlin Standard Library. Here are a few examples:

  • The synchronized method
  • Precondition functions (require, check, etc.)
  • Standard‑Library scope functions (also, apply, let, etc.)
  • String utilities isNullOrBlank and isNullOrEmpty (so strings can be smart‑cast as non‑null if these return false)
  • The collection utility isNullOrEmpty
  • Result class extensions

Conclusion

Contracts can make your code more readable, but use them carefully because the compiler won’t verify they’re correct, it just trusts you on that. They’re especially useful at the library level, but they can also be handy in application code when you need to help the compiler understand your intent.