skip to content

returns Effects & Smart Casts

The returns-implies effects are what let requireNotNull and isNullOrEmpty keep a smart cast alive past the call. Writing one for your own validation helper is a satisfying answer to 'why does my check not narrow the type'.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

Explain the difference between returns(), returns(true), returns(false), and returnsNotNull() in a Kotlin contract. Give a stdlib or realistic example where each is the right choice.

level: middleimportance: should knowfreq 45%

basics

~10 s

They describe which return outcomes guarantee a condition. returns() means 'any normal return'; returns(true)/returns(false) tie the guarantee to a specific Boolean result; returnsNotNull() ties it to returning a non-null value.

open as a page

Write a custom function with a returns-effect contract so callers get a smart cast, and list the rules/limitations the contract block must obey (placement, opt-in, what conditions are allowed).

level: middleimportance: should knowfreq 38%

basics

~20 s

Put a contract { } block as the very first statement, opt in with the experimental annotation, and use returns(...) implies (param is Type / param != null). Then callers can use the parameter as the narrowed type after the call.

open as a page

Kotlin contracts are unverified by the compiler. Explain the soundness risk of returns effects, when smart casts driven by a contract can silently break, and how this constrains where you should declare them.

level: seniorimportance: should knowfreq 28%

basics

~20 s

The compiler believes your contract without checking the body. If the body doesn't actually enforce the promised condition, the compiler smart-casts based on a false promise, which can throw at runtime. So contracts must be written carefully and kept in sync with the body.

open as a page

Compare contract-based narrowing (requireNotNull, isNullOrEmpty) with alternatives like the Elvis operator, !!, and plain if-checks for null/type narrowing. When does the contract approach earn its keep, and what does it cost?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Contracts let helper functions carry the narrowing so callers stay clean and get a real smart cast plus a clear exception. Elvis and if-checks narrow inline without any helper; !! narrows but throws a vague NPE. Contracts cost an experimental opt-in and unverified trust.

open as a page