Table of Contents
Preface
The Kotlin compiler is pretty smart about static analysis, but there are still times when code looks perfectly obvious to a programmer, but is very hard for the compiler to figure out. Contracts were introduced in Kotlin 1.3 to solve exactly that. You can use them to tell the compiler your intent in situations where it can’t be sure, clearing up ambiguity so it can do its job.
Example
Consider this code:
fun main() {
signIn("qwerty")
}
fun signIn(password: String?) {
if (validatePassword(password)) {
println("Password starts with ${password[0]}")
// Sign in
}
}
fun validatePassword(password: String?): Boolean {
return password != null && password.length > 0
}
This code looks right to a programmer, but the Kotlin compiler will throw an error. The reason is: Kotlin can’t track nullability checks across function boundaries. So even if password was validated in a called function, the compiler still sees it as nullable here.
Solution
Since Kotlin 1.3, we can explicitly define the relationship between a function’s inputs and its outcome so the compiler is confident enough to perform a smart cast. Let’s modify our example to do that:
fun validatePassword(password: String?): Boolean {
contract {
returns(true) implies (password != null)
}
return password != null && password.length > 0
}
Now the compiler has a little more information about what we’re doing. Because we called validatePassword and it returned true, the compiler won’t complain about password being nullable here.
Another Example
In this example, we’re going to try to initialize a value inside a block of code:
fun main() {
val result: Int
executeCode {
result = 1
}
println(result)
}
inline fun executeCode(block: () -> Unit) {
block()
}
This code looks innocent to a programmer, but the Kotlin compiler won’t run it. Let’s see what it says:
- Captured values initialization is forbidden due to possible reassignment
- Variable
resultmust be initialized
Solution
Luckily, there’s a special type of contract that lets us tell the compiler exactly how many times a given block of code will be called. Let’s modify our executeCode method:
inline fun executeCode(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
Now we can compile and run our code without getting any errors.
Real‑World Usage
Contracts are already used in the Kotlin Standard Library. Here are a few examples:
- The
synchronizedmethod - Precondition functions (
require,check, etc.) - Standard‑Library scope functions (
also,apply,let, etc.) - String utilities
isNullOrBlankandisNullOrEmpty(so strings can be smart‑cast as non‑null if these returnfalse) - The collection utility
isNullOrEmpty Resultclass extensions
Conclusion
Contracts can make your code more readable, but use them carefully because the compiler won’t verify they’re correct, it just trusts you on that. They’re especially useful at the library level, but they can also be handy in application code when you need to help the compiler understand your intent.