How do EK9 constants compare to const, final, and static in other languages?

← Constants and Immutability · Ref: Q143

Developers frequently confuse const, final, and static because these keywords overlap differently across languages. Here is how each language handles constants and what EK9 does differently.

THE CONFUSION

The word 'constant' should mean 'this value never changes'. But most languages only guarantee 'this binding cannot be reassigned', which is fundamentally different. A final reference to a mutable object does not make the object constant.

JAVA

Java uses 'final' to prevent reassignment, but final does NOT prevent mutation:

  final List<String> names = new ArrayList<>();
  names.add("Alice"); // This works! The list is mutated.

The 'static final' idiom is Java's convention for constants, but even static final objects can be mutated if they are mutable types. Only primitive static final fields are truly constant (inlined by the compiler).

RUST

Rust's 'const' is truly compile-time: the value must be computable at compile time and is limited to scalar-like types. 'static' is runtime-initialized and lives for the program lifetime. 'let' bindings are immutable by default but interior mutability (RefCell, Mutex) can bypass this. Rust comes closest to real immutability but still has escape hatches.

GO

Go's 'const' is compile-time only and limited to basic types: numbers, strings, and booleans. No structs, slices, or maps. Everything else is a 'var' that you promise not to change, with no compiler enforcement.

KOTLIN

Kotlin's 'const val' is compile-time, limited to primitives and String. 'val' prevents reassignment but not mutation: val list = mutableListOf() still allows list.add(). Similar to Java's final.

JAVASCRIPT

JavaScript's 'const' is arguably the most misleading: it prevents reassignment but not mutation. const obj = {}; obj.x = 1 works perfectly. The name 'const' suggests immutability but delivers only binding fixity.

EK9

EK9's 'defines constant' block provides true value immutability through copy-on-access:

  1. Every access returns a fresh independent copy via copy constructor
  2. All mutation operators are blocked at compile time (NOT_MUTABLE error)
  3. Supports 18 types including domain types (Money, Colour, Duration, Dimension)
  4. No escape hatch: you cannot mutate a constant through any mechanism

The key insight is that assigning a constant to a variable gives you a mutable copy. The constant itself is forever protected, but the copy is yours to modify freely.

KEY INSIGHT

Most languages conflate 'immutable binding' with 'immutable value'. EK9 separates them: the constant value is immutable (copy-on-access), while variables that receive copies are fully mutable. This eliminates the confusion entirely.

See Q140 for defining constants in EK9. See Q141 for copy-on-access immutability details. See Q130 for mutable vs immutable collection operations.

Example

defines module qa.constants.comparison

  defines constant
    greeting <- "Hello"
    count <- 42
    price <- 9.99#USD
    colour <- #FF8800

  defines program

    ConstFinalStaticDemo()
      stdout <- Stdout()

      // === EK9: CONSTANT ACCESS GIVES YOU A COPY ===

      stdout.println(`Constant: ${greeting}`)
      stdout.println(`Count: ${count}`)

      // === VARIABLE FROM CONSTANT IS MUTABLE ===

      name <- greeting
      name += " World"
      stdout.println(`Modified copy: ${name}`)

      // Constant unchanged
      stdout.println(`Constant still: ${greeting}`)

      // === EXPRESSIONS WITH CONSTANTS PRODUCE MUTABLE RESULTS ===

      doubled <- count + count
      stdout.println(`Doubled: ${doubled}`)

      doubled := doubled + 1
      stdout.println(`Incremented copy: ${doubled}`)

      // Original constant unchanged
      stdout.println(`Count still: ${count}`)

      // === DOMAIN TYPE CONSTANTS ===

      stdout.println(`Price: ${price}`)
      stdout.println(`Colour: ${colour}`)

      localPrice <- price
      stdout.println(`Local price copy: ${localPrice}`)

Common mistakes

E07890 — Unlike Java's final which only prevents reassignment, EK9 constants block ALL mutation operators including +=. Assign the constant to a mutable variable first, then modify the copy. See ek9 -h E07890 for details.

Incorrect:

greeting += " World"

Correct:

name <- greeting

E07890 — Constants cannot be reassigned. The := operator mutates the target variable, which is forbidden for constants. Copy the constant to a local variable first. See ek9 -h E07890 for details.

Incorrect:

count := count + 1

Correct:

doubled := doubled + 1

E07890 — The :=? guarded assignment still mutates the target when it is unset. EK9 constants cannot be mutated through any mechanism including guarded assignment. Constants are always set, so :=? would never trigger anyway. See ek9 -h E07890 for details.

Incorrect:

greeting :=? "World"

Correct:

name <- greeting
Other ways to ask this
  • What is the difference between const and final?
  • How does EK9 const compare to Java final?
  • Is EK9 const like Rust const or JavaScript const?

Coming from another language?

Java: final prevents reassignment not mutation, static final is convention for constants. Rust: const is compile-time only (scalar types), let is immutable binding with interior mutability escape. Go: const limited to basic types, no struct/slice/map. Kotlin: const val for primitives/String only, val prevents reassignment not mutation. JavaScript: const prevents reassignment not mutation (most misleading). EK9: copy-on-access true immutability for 18 types, no escape hatch, variable copies are mutable.

Keywords: java, immutable, final, comparison, reassignment, migrate, mutation, go, const, static, constant, rust