skip to content

Walk through the opt-in mechanics for the contracts DSL: what makes it experimental, how do you opt in at function vs module level, and what does that imply for a published API?

level: seniorimportance: nice to knowfreq 16%

answer

  1. @ExperimentalContracts is a @RequiresOptIn marker
  2. @OptIn on function/file vs -opt-in module flag
  3. @OptIn is non-propagating
  4. Callers need no opt-in
  5. Stdlib ships contracts despite experimental status

basics

~20 s

The contract { } function is marked experimental, so the compiler warns or errors unless you opt in. You can opt in on one function with @OptIn, or for the whole module with a compiler flag. Either way you accept that the API could change.

solid answer

~40 s

kotlin.contracts.contract is annotated @ExperimentalContracts, which is itself a @RequiresOptIn marker. The opt-in system means any usage requires explicit acknowledgement: annotate the declaring function (or an enclosing element) with @OptIn(ExperimentalContracts::class), or opt in module-wide via the Gradle compiler argument -opt-in=kotlin.contracts.ExperimentalContracts (e.g. compilerOptions.optIn.add(...)). @OptIn is propagation-free — it does not force callers to opt in, because using a contracted function is not itself using the experimental API; only authoring the contract is. So a library can ship contracted functions and consumers see normal smart-cast behavior without any opt-in of their own. The implication: you accept that the contracts DSL surface is unstable across Kotlin versions, even though stdlib has used it in stable releases for years.

code

kotlin · 7 lines
kotlin
// Module-wide opt-in in build.gradle.kts:
kotlin {
    compilerOptions {
        optIn.add("kotlin.contracts.ExperimentalContracts")
    }
}
// Now no per-function @OptIn is needed to declare contract { }

go deeper

for a junior

Knows you need @OptIn(ExperimentalContracts::class) on the function.

for a middle

Can opt in at both function and module level and knows the marker name.

for a senior

Explains that @OptIn is non-propagating so callers are unaffected, and the author bears the experimental risk.

for a principal

Sets module policy (per-use vs module flag), reasons about cross-version DSL drift, and the stable-stdlib-vs-experimental-DSL tension for a published library.

## What makes it experimental The top-level `contract { }` function lives in `kotlin.contracts` and is annotated `@ExperimentalContracts`. That annotation is declared with `@RequiresOptIn`, Kotlin's general mechanism for marking APIs whose stability is not guaranteed. Touching such an API without acknowledgement produces a compiler error (or warning, depending on the marker's level). ## Two ways to opt in ### Function / declaration level ```kotlin import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @OptIn(ExperimentalContracts::class) fun isNonNull(x: Any?): Boolean { contract { returns(true) implies (x != null) } return x != null } ``` `@OptIn(...)` placed on the function (or its class/file) acknowledges the experimental usage for that scope. ### Module level (Gradle) ```kotlin // build.gradle.kts kotlin { compilerOptions { optIn.add("kotlin.contracts.ExperimentalContracts") } } ``` or the raw flag `-opt-in=kotlin.contracts.ExperimentalContracts`. This opts in every file in the module so you do not repeat `@OptIn`. ## Opt-in does not propagate to callers This is the key subtlety. `@OptIn` is **non-propagating**: it does not annotate your function as experimental. Only **declaring** a contract uses the experimental API; **calling** a function that happens to have a contract does not. Therefore: - A library can publish functions that declare contracts. - Consumers call them and enjoy the smart casts / definite assignment **without** any opt-in. - The experimental obligation stays entirely on the author. Contrast with `@RequiresOptIn` markers you might define yourself and want to propagate — for those you'd use `@MyMarker` on the API instead of `@OptIn`. ## Implications for a published API - You are taking on the risk that the contracts DSL syntax could change between Kotlin versions; you'd then have to update your declarations (your public signatures and runtime behavior stay the same). - Because the obligation does not leak to consumers, shipping contracts is low-friction for them. - The stdlib itself opts in internally and ships contracts in stable releases, which is strong evidence the feature is dependable in practice even while formally experimental. ## Quick reference - Marker: `kotlin.contracts.ExperimentalContracts` (`@RequiresOptIn`). - Per-use: `@OptIn(ExperimentalContracts::class)`. - Module: `-opt-in=kotlin.contracts.ExperimentalContracts`. - Propagation: none — callers unaffected.

  • If my library function declares a contract, must consumers also opt in?
    No. @OptIn is non-propagating and calling a contracted function is not an experimental usage, so consumers need nothing.
  • What is the difference between @OptIn and propagating a @RequiresOptIn marker?
    @OptIn acknowledges and contains the experimental usage locally; annotating the API with the marker itself would force callers to opt in. Contracts use the former.

saying these in an interview costs you the question

  • Claiming callers must opt in to use contracted functions
  • Confusing @OptIn (non-propagating) with re-annotating the API as experimental
  • Not knowing the module-level -opt-in flag exists
  • Thinking experimental means the feature is unusable in production

context