Contracts — Smart Cast Effects
Use Kotlin contracts to help the compiler with smart casting: returns, callsInPlace, and custom implications.
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.
Context Receivers — Scoped Functions
Use context receivers for scoped function access: transaction contexts, logging contexts, and DSL building.
Best for: Transaction and logging scope management
Null Safety — Elvis, Safe Call, and let
Master Kotlin null safety: safe calls, Elvis operator, let/also scoping, and smart casts.
Best for: Safe navigation through nullable chains
Data Classes — Copy, Destructure, and Equals
Use data classes for immutable models: auto-generated equals, hashCode, copy, and destructuring.
Best for: Immutable domain models and DTOs
Extension Functions and Properties
Add methods to existing classes without inheritance: extension functions, properties, and generic extensions.
Best for: Adding utility methods to third-party types