Igor Bubelov

About Blog Notes Photos Portfolio

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

Programming   Kotlin

This page doesn't show ads and the reasons are simple:

  • Most people don't want to see ads (what a surprise)
  • Ads can track you and violate your privacy
  • Ads is the main reason why many websites are so slow

If you find this content valuable or you want to see more content like this, you can leave a tip with bitcoin:

34CXtg7c4Vbw8DZjAwFQVsrbu9eDEbTzbA
bitcoin tips QR