Let's craft software. Contact me
Avatar

Héctor Valls

Sr. Software Engineer

Cover Image for Kotlin Contracts

Kotlin Contracts

Kotlin has an awesome compiler, capable of infer types and manage nullability in most situations. But there are scenarios where it still has limitations. Consider this code:

interface Event

class ProductEvent(val productId: String) : Event

class ProductEventHandler {

    fun handle(event: Event) {
        if (event is ProductEvent) {
            println(event.productId) // ✅ This compiles
        }
    }
}

Kotlin detects the type checking in the 'if' condition and it is able to perform a smartcast of 'event' parameter from 'Event' to 'ProductEvent'.

Now, imagine we want to extract that type check logic to another method, 'shouldHandle', like below:

class ProductEventHandler {

    fun handle(event: Event) {
        if (shouldHandle(event)) {
            println(event.productId) // ❌ This crashes
        }
    }

    fun shouldHandle(event: Event): Boolean {
        return event is ProductEvent
    }

}

This doesn't compile anymore. Compiler is not smart enough to understand it.

Kotlin version 1.3.0 introduced an experimental feature called "contracts" to handle this. By using contracts, we can explicitly describe a function behavior in order to help compiler to deal with this kind of scenarios.

In our example, we want to tell compiler "if 'shouldHandle' function returns true, it means that 'event' parameter is indeed a 'ProductEvent'". Let's see the code:

class ProductEventHandler {

    fun handle(event: Event) {
        if (shouldHandle(event)) {
            println(event.productId) // ✅ This compiles
        }
    }

    fun shouldHandle(event: Event): Boolean {
        contract {
            returns(true) implies (event is ProductEvent)
        }
        return event is ProductEvent
    }

}

Easy, right?

Besides returns, there are some other useful effects you can use. Visit the official docs to learn more about Kotlin contracts.

At this time, contracts is an experimental feature and it is under development. You will need use @ExperimentalContracts annotation to use them.