kotlinadvanced

Contracts — Smart Cast Effects

Use Kotlin contracts to help the compiler with smart casting: returns, callsInPlace, and custom implications.

kotlin
import kotlin.contracts.*

// Contract: returns true implies parameter is non-null
@OptIn(ExperimentalContracts::class)
fun String?.isNotNullOrEmpty(): Boolean {
    contract {
        returns(true) implies (this@isNotNullOrEmpty != null)
    }
    return this != null && this.isNotEmpty()
}

// Contract: returns true implies type check
@OptIn(ExperimentalContracts::class)
fun Any?.isString(): Boolean {
    contract {
        returns(true) implies (this@isString is String)
    }
    return this is String
}

// CallsInPlace contract (like stdlib's run, let, etc)
@OptIn(ExperimentalContracts::class)
inline fun <T> requireAndUse(value: T?, block: (T) -> Unit) {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    requireNotNull(value)
    block(value)
}

// Contract with custom assertion
@OptIn(ExperimentalContracts::class)
fun assertNotNull(value: Any?, message: String = "Value must not be null") {
    contract {
        returns() implies (value != null)
    }
    if (value == null) throw AssertionError(message)
}

// Contract-enabled builder
@OptIn(ExperimentalContracts::class)
inline fun buildConfig(builder: MutableMap<String, Any>.() -> Unit): Map<String, Any> {
    contract {
        callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
    }
    return buildMap(builder)
}

// Combined type narrowing
@OptIn(ExperimentalContracts::class)
fun validateInput(input: Any?): Boolean {
    contract {
        returns(true) implies (input is String)
    }
    return input is String && input.length in 1..100
}

fun main() {
    // isNotNullOrEmpty smart cast
    val name: String? = "Alice"
    if (name.isNotNullOrEmpty()) {
        // Compiler knows name is non-null here!
        println("Name length: ${name.length}")
    }

    // isString smart cast
    val value: Any? = "Hello"
    if (value.isString()) {
        // Compiler knows value is String here
        println("String value: ${value.uppercase()}")
    }

    // requireAndUse — val can be initialized in lambda
    val result: String
    requireAndUse("hello") { v ->
        result = v.uppercase() // works because callsInPlace(EXACTLY_ONCE)
    }
    println("Result: $result")

    // assertNotNull
    val data: String? = "data"
    assertNotNull(data)
    println("Data: ${data.length}") // smart cast: data is String

    // buildConfig with EXACTLY_ONCE
    val initialized: Boolean
    val config = buildConfig {
        put("host", "localhost")
        put("port", 8080)
        initialized = true // works!
    }
    println("Config: $config, initialized: $initialized")

    // validateInput
    val input: Any? = "valid input"
    if (validateInput(input)) {
        println("Valid string: ${input.uppercase()}") // smart cast
    }

    // Null check chain
    val email: String? = "test@example.com"
    val domain: String? = email?.substringAfter("@")
    if (domain.isNotNullOrEmpty()) {
        println("Domain: ${domain.uppercase()}") // smart cast
    }
}

Use Cases

  • Custom null-safety assertions
  • Type-narrowing helper functions
  • Variable initialization in lambdas

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.