How does EK9 protect constants from mutation?
← Constants and Immutability · Ref: Q141
EK9 enforces deep immutability for constants through two mechanisms: copy-on-access and compile-time mutation blocking. Every reference to a constant produces an independent copy, and all mutation operators are blocked at compile time.
COPY-ON-ACCESS
Every time you reference a constant, the compiler generates code that loads the constant value and immediately creates a fresh copy via the type's copy constructor. This means each access gets an independent copy. If you write:
msg <- greeting
The variable msg receives a copy of the greeting constant, not a reference to the same object.
DEEP IMMUTABILITY
All mutation operators are blocked on constants at compile time with error NOT_MUTABLE:
++ -- += := :=? :=: :~: :^:
Attempting any of these on a constant produces a compile error. This is not a runtime check; the compiler catches it before any code is generated.
AUTO-CLONING ON PASS
When you pass a constant as a function argument, the compiler auto-clones it. The function receives a copy, so the original constant value is unaffected regardless of what the function does with its parameter.
AUTO-CLONING ON RETURN
When a constant is used in an expression or returned from a function, the result is always a fresh mutable copy. This means you can freely use constants in computations without risk.
VARIABLE COPIES ARE MUTABLE
Once you assign a constant to a variable, the variable holds an independent mutable copy:
name <- greeting name += " World"
The variable name is mutable and can be modified. The greeting constant remains unchanged.
WHY ONLY BUILT-IN TYPES
User-defined classes and records cannot be constants because copy-on-access requires the compiler to know exactly how to copy the value. Built-in types have guaranteed copy constructors with well-defined semantics. For user-defined types, the compiler cannot guarantee that a copy is truly independent, so it restricts constants to the 18 built-in value types.
See Q140 for defining constants and the full list of constant-eligible types. See Q130 for mutable vs immutable collection operations. See Q29 for unset/set semantics and how constants are always set. See Q143 for how this compares to const/final/static in other languages. See Q268 for how immutability prevents OWASP data integrity vulnerabilities. See Q317 for magic literal detection that encourages named constants.
Example
#!ek9 defines module qa.constants.immutability defines constant greeting <- "Hello" baseCount <- 10 price <- 9.99#USD defines function addSuffix() -> text as String <- rtn as String: text + " World" doubleIt() -> n as Integer <- rtn as Integer: n + n defines program ConstantImmutabilityDemo() stdout <- Stdout() // === COPY-ON-ACCESS: each reference is a fresh copy === first <- greeting second <- greeting stdout.println(`First: ${first}`) stdout.println(`Second: ${second}`) // === VARIABLE COPIES ARE MUTABLE === mutableName <- greeting mutableName += " World" stdout.println(`Modified copy: ${mutableName}`) // Original constant unchanged on next access stdout.println(`Constant still: ${greeting}`) // === CONSTANTS IN EXPRESSIONS === doubled <- baseCount + baseCount stdout.println(`Doubled: ${doubled}`) stdout.println(`Original: ${baseCount}`) // === CONSTANTS PASSED TO FUNCTIONS === result <- addSuffix(greeting) stdout.println(`Function result: ${result}`) stdout.println(`Constant after call: ${greeting}`) computed <- doubleIt(baseCount) stdout.println(`Computed: ${computed}`) stdout.println(`Base still: ${baseCount}`) // === CONSTANTS IN STRING INTERPOLATION === stdout.println(`The price is ${price}`)
Common mistakes
E07890 — Constants are protected by copy-on-access immutability. You cannot use mutating operators like += on a constant. Use the constant in a non-mutating expression to produce a new value instead. See ek9 -h E07890 for details.
Incorrect:
baseCount += 10
Correct:
doubled <- baseCount + baseCount
E07890 — Constants cannot be reassigned with :=. Unlike Java final or JavaScript const which only prevent reassignment, EK9 constants block ALL mutation including assignment. Create a new variable from the constant instead. See ek9 -h E07890 for details.
Incorrect:
baseCount := 20
Correct:
computed <- doubleIt(baseCount)
E07890 — The ++ operator mutates the target variable. Constants cannot be mutated by any operator. Use the constant in a non-mutating expression to compute a new value. See ek9 -h E07890 for details.
Incorrect:
greeting++
Correct:
mutableName <- greeting
Other ways to ask this
- Can I modify constants in EK9?
- Why are constants copied on access?
- How does copy-on-access work for constants?
Coming from another language?
Java: final prevents reassignment but not mutation (final List can still add()). JavaScript: const prevents reassignment but not mutation (const obj = {}; obj.x = 1 works). Rust: const is compile-time only, let bindings are immutable by default. Go: const limited to basic types. EK9: copy-on-access ensures every reference gets a fresh independent copy, plus all mutation operators are blocked at compile time. Truly immutable, no escape hatch.
Keywords: access, clone, constant, copy, deep, immutable, mutation, protection, safety