skip to content

After calling require(x is String) or x?.let { ... } in stdlib, the compiler often lets you treat x as the narrowed type without an extra cast. What language feature makes the standard library functions like require, check, and requireNotNull propagate type information to the caller?

level: juniorimportance: must knowfreq 55%

answer

  1. Contracts = compiler-readable promises
  2. returns() implies (condition)
  3. require/check/requireNotNull carry contracts
  4. Enables smart cast after the call
  5. contract{} is the first statement

basics

~10 s

Kotlin contracts. The stdlib functions declare a promise about how they behave, so the compiler knows that if the call returns normally, a condition is true. It then smart-casts the variable for you.

solid answer

~40 s

It is the Kotlin contracts feature. Functions such as require, check, requireNotNull, checkNotNull, and isNullOrEmpty have a contract { } block describing returns effects. For example require uses returns() implies (condition): if the function returns normally, the condition must be true. So after require(x is String), the compiler smart-casts x to String because the only way execution continued is if x is String was true (require throws IllegalArgumentException otherwise). requireNotNull uses returns() implies (value != null) plus returnsNotNull semantics, so the value is treated as non-null afterward. Contracts let the compiler reason about effects it could not infer from the body alone, eliminating redundant null checks and casts.

code

kotlin · 5 lines
kotlin
fun describe(value: Any?) {
    requireNotNull(value)      // implies value != null
    require(value is String)   // implies value is String
    println(value.length)      // value smart-cast to String
}

go deeper

for a junior

Knows that require/requireNotNull let you skip a manual cast or null check and names 'contracts' as the reason.

for a middle

Can write returns() implies (condition) and explains that the call must return normally (not throw) for the fact to hold.

for a senior

Distinguishes returns(), returns(true/false), returnsNotNull(); knows smart-cast stability requirements interact with this.

for a principal

Discusses that contracts are a trust-based, experimental opt-in and the compiler does not verify the body matches the contract.

## The problem contracts solve The Kotlin compiler does **smart casts**: after `if (x is String) { ... }` it lets you use `x` as `String` inside the branch without an explicit cast. But the compiler only sees this when the check is *inline* in the code it analyzes. When you delegate the check to a **function**, the compiler normally cannot see inside that function's body to know what the call guarantees. ```kotlin fun handle(x: Any?) { require(x is String) // throws IllegalArgumentException if false println(x.length) // x is smart-cast to String here } ``` Without contracts, `x.length` would not compile, because the compiler would not know that surviving the `require` call implies `x is String`. ## What a contract is A **contract** is a machine-readable promise a function makes to the compiler about its effects. It is declared with the experimental `contract { }` DSL as the first statement of the function body. The relevant effect here is the **returns effect**: - `returns() implies (condition)` — *if the function returns normally* (does not throw), then `condition` holds. - `returns(true) implies (condition)` / `returns(false) implies (condition)` — ties a specific Boolean return value to a condition. - `returnsNotNull() implies (condition)` — if the function returns a non-null value, the condition holds. ## How stdlib uses it `require`, `check`, `requireNotNull`, `checkNotNull`, `isNullOrEmpty`, `isNullOrBlank` all ship with such contracts: ```kotlin // simplified from the Kotlin stdlib public inline fun require(value: Boolean) { contract { returns() implies value } if (!value) throw IllegalArgumentException("Failed requirement.") } ``` So `require(x is String)` means: "if this call returns, then `x is String` is true." The compiler trusts that and smart-casts `x` to `String` on every line after the call. `requireNotNull(x)` returns a non-null value and also implies `x != null`, removing the need for `!!` or `?.`. ## Key keywords - `contract { }` — the DSL block - `implies` — links a return mode to a condition - `returns()`, `returns(true/false)`, `returnsNotNull()` — the return-mode predicates - These work because the functions are usually `inline`, but inlining is not strictly required for returns effects.

  • Does the smart cast still work if x is a mutable var captured in a closure?
    No. Smart casts (contract-driven or not) require a stable value: a val, or a local var not modified by a closure. A captured/mutable var cannot be smart-cast because it might change between the check and the use.
  • What exception does require throw versus check?
    require throws IllegalArgumentException (for argument/precondition validation); check throws IllegalStateException (for state invariants). Their contracts are identical: returns() implies value.

A contract is like a signed receipt: the function hands the compiler a note saying 'if you got past me, this fact is true,' and the compiler files it away.

saying these in an interview costs you the question

  • Claiming the compiler 'reads the function body' to infer this (it reads the contract, not the body)
  • Saying require returns the value (require returns Unit; requireNotNull returns the value)
  • Confusing this with reflection or runtime type checks
  • Thinking it only works inside if-blocks

context