official kotlin language logo

Helping the Kotlin Compiler: Mastering Contracts for Smarter Smart-Casts

The Problem: Compiler Blindness

Kotlin is famous for its smart-casting. Usually, if you check if a variable is not null, the compiler is smart enough to treat that variable as non-nullable within that scope. However, as soon as you move that logic into a helper function, the compiler loses its way. Even if your function validateInput() ensures a value isn't null, the compiler still forces you to use the safe-call operator or double-bangs afterward.

This is where Kotlin Contracts come in. They allow you to explicitly tell the compiler the relationship between a function's arguments and its return value.

A Real-World Scenario: Smart Home Commands

Imagine you are building a system for a smart home. You have a Command object that might contain a payload. You want a utility function to check if a command is valid before processing it. Without contracts, your code looks repetitive.

class Command(val action: String?, val priority: Int?)

fun isValid(command: Command?): Boolean {
    return command != null && command.action != null
}

fun process(command: Command?) {
    if (isValid(command)) {
        // The compiler doesn't know 'command' is non-null here!
        println(command!!.action.length) 
    }
}

Implementing Your First Contract

To fix this, we use the contract DSL. This requires the @ExperimentalContracts annotation, as the API is still stabilizing, but it is widely used in the Kotlin standard library itself.

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
fun isValid(command: Command?): Boolean {
    contract {
        returns(true) implies (command != null)
    }
    return command != null && command.action != null
}

fun process(command: Command?) {
    if (isValid(command)) {
        // Now the compiler knows 'command' is safe!
        println(command.action?.length)
    }
}

In the example above, returns(true) implies (command != null) tells the compiler: "If this function returns true, you can be 100% certain that the command object is not null."

Beyond Nullability: Type Checking

Contracts aren't just for null checks. They are incredibly useful for type checking within custom logic. Suppose you have a sealed class for different types of sensor data, and you want a function that verifies if a sensor is currently "Active."

@OptIn(ExperimentalContracts::class)
fun isMotionSensor(sensor: Sensor): Boolean {
    contract {
        returns(true) implies (sensor is Sensor.Motion)
    }
    return sensor is Sensor.Motion
}

fun handle(sensor: Sensor) {
    if (isMotionSensor(sensor)) {
        // Automatic smart-cast to Sensor.Motion
        println("Detected movement at: ${sensor.location}")
    }
}

Best Practices and Limitations

While powerful, contracts have rules. First, the contract block must be the very first statement in your function. Second, the compiler doesn't actually "verify" that your logic matches the contract; it trusts you blindly. If you claim a function implies a non-null value but your logic returns true for a null value, you will trigger a NullPointerException at runtime.

Kotlin Contracts are the bridge between your custom validation logic and the compiler's type system. By using them, you eliminate redundant checks and make your codebase feel more native and robust.