Skip to main content

Kotlin Contract DSL

Kotlin's Contract DSL (Domain Specific Language) is an advanced feature that allows developers to provide hints to the compiler about the behavior of functions. These "contracts" help the compiler make smarter decisions during static analysis, leading to better type inference, null safety checks, and overall code optimization.

Introduction to Contracts

When you write code in Kotlin, the compiler analyzes your code to ensure type safety and identify potential issues. However, there are scenarios where the compiler cannot infer certain behaviors or guarantees without additional information. This is where contracts come in.

Contracts allow you to explicitly tell the compiler about:

  • Function invocation effects
  • Return value conditions
  • Relationships between function parameters and results
caution

Contracts are still an experimental feature in Kotlin, so you'll need to opt-in to use them.

Setting Up for Contracts

Before using contracts, you need to enable experimental features:

kotlin
@OptIn(ExperimentalContracts::class)
fun myFunctionWithContracts() {
// Function using contracts
}

Or at file level:

kotlin
@file:OptIn(ExperimentalContracts::class)
package com.example.myapp

Basic Contract Syntax

The basic structure of a contract looks like this:

kotlin
import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
fun myFunction(param: Any?) {
contract {
// Contract statements go here
}

// The rest of the function implementation
}

Common Contract Types

1. Returns Contracts

Returns contracts specify conditions about when a function returns normally:

kotlin
@OptIn(ExperimentalContracts::class)
fun String?.isNotNull(): Boolean {
contract {
returns(true) implies (this@isNotNull != null)
}
return this != null
}

// Usage
fun processString(str: String?) {
if (str.isNotNull()) {
// The compiler now knows str is not null
println(str.length) // No need for str!! or str?.length
}
}

In this example, the contract tells the compiler that if isNotNull() returns true, then this is not null, allowing for smart casts.

2. CallsInPlace Contracts

This type of contract informs the compiler how many times a lambda parameter will be invoked:

kotlin
@OptIn(ExperimentalContracts::class)
inline fun executeOnce(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}

fun example() {
var x: String

executeOnce {
x = "Hello" // Compiler knows this is assigned exactly once
}

println(x) // No "variable might not have been initialized" error
}

The InvocationKind enum has four values:

  • EXACTLY_ONCE: The lambda is called exactly once
  • AT_MOST_ONCE: The lambda is called zero or one times
  • AT_LEAST_ONCE: The lambda is called one or more times
  • UNKNOWN: No guarantee about invocation (default)

Real-World Applications

Example 1: Improved Smart Casting

Contracts are particularly useful for custom utility functions where you want to maintain smart casting:

kotlin
@OptIn(ExperimentalContracts::class)
fun ensureNotNull(value: Any?): Boolean {
contract {
returns(true) implies (value != null)
}

return value != null
}

fun processData(data: String?) {
if (ensureNotNull(data)) {
// Without contracts, the compiler wouldn't know data is non-null here
println("Data length: ${data.length}")
} else {
println("No data provided")
}
}

Example 2: Resource Management

Contracts can help with ensuring proper resource management:

kotlin
@OptIn(ExperimentalContracts::class)
inline fun <T : AutoCloseable, R> T.use(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}

var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when {
exception == null -> close()
else -> try {
close()
} catch (closeException: Throwable) {
// Suppressed exception handling
}
}
}
}

// Usage
fun readFirstLine(filePath: String): String {
val reader = java.io.BufferedReader(java.io.FileReader(filePath))
reader.use { r ->
return r.readLine() ?: ""
}
// Compiler knows the lambda is called exactly once and the resource is properly closed
}

Example 3: Custom Control Flow Functions

You can create your own control flow functions with contracts:

kotlin
@OptIn(ExperimentalContracts::class)
inline fun runIf(condition: Boolean, block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}

if (condition) {
block()
}
}

fun demo() {
var message: String? = null

runIf(true) {
message = "Hello, Contracts!"
}

// Without contracts, the compiler wouldn't know message might be initialized
println(message?.length ?: 0)
}

Advanced Contract Usage

Combining Multiple Contract Clauses

You can combine multiple contract statements for more complex behaviors:

kotlin
@OptIn(ExperimentalContracts::class)
fun validate(value: String?, process: (String) -> Unit): Boolean {
contract {
returns(true) implies (value != null)
callsInPlace(process, InvocationKind.AT_MOST_ONCE)
}

if (value != null) {
process(value)
return true
}
return false
}

Limitations of Contracts

Contracts have some important limitations to keep in mind:

  1. Contracts must be at the very beginning of a function
  2. The condition in a contract must be directly related to function parameters or the receiver
  3. Contracts are still experimental and might change in future Kotlin versions
  4. Function bodies must match the contract statements (the compiler doesn't verify this)

When to Use Contracts

Use contracts when:

  • You're creating utility functions that perform checks (like null checks)
  • You need to preserve smart casting in custom control flow functions
  • You're building a DSL or library where type inference is important
  • You're working with lambda callbacks where execution guarantees are needed

Avoid overusing contracts for simple functions where they don't provide significant benefits.

Summary

Kotlin's Contract DSL provides a powerful way to give the compiler additional information about your code's behavior. By using contracts, you can:

  • Improve smart casting for custom functions
  • Ensure proper lambda invocation
  • Create more expressive and type-safe APIs
  • Help the compiler optimize your code

While contracts are still experimental, they offer significant advantages for library developers and those building complex applications where type safety and static analysis are critical.

Additional Resources

Exercises

  1. Create a custom requireNotNull function with contracts that improves on the standard library version.
  2. Implement a runAtMostOnce function that guarantees a lambda is executed no more than once.
  3. Write a contract for a function that checks if a collection is not empty and allows smart casting for the collection.
  4. Create a simple resource management utility with contracts that ensures proper resource cleanup.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)