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?
answer
- Contracts = compiler-readable promises
- returns() implies (condition)
- require/check/requireNotNull carry contracts
- Enables smart cast after the call
- contract{} is the first statement
basics
~10 sKotlin 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 sIt 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 linesfun 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
Knows that require/requireNotNull let you skip a manual cast or null check and names 'contracts' as the reason.
Can write returns() implies (condition) and explains that the call must return normally (not throw) for the fact to hold.
Distinguishes returns(), returns(true/false), returnsNotNull(); knows smart-cast stability requirements interact with this.
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