Why does EK9 not have primitives?

← Getting Started · Ref: Q26

EK9 has no primitives. Every value is an object. There is no autoboxing, no unboxing, no dual representation. Integer is an object. Float is an object. Boolean is an object. Character is an object. Always.

You cannot use primitives instead of objects because primitives do not exist in EK9. This is a deliberate design decision that eliminates an entire class of bugs and inconsistencies found in other languages.

WHY OTHER LANGUAGES HAVE PRIMITIVES (AND THE PROBLEMS THEY CAUSE)

Java has two type systems that do not mix cleanly. int is a primitive, Integer is an object. This creates:

  Autoboxing surprises: Integer a = 127; Integer b = 127; a == b is true. Integer a = 128; Integer b = 128; a == b is false. The JVM caches small Integer objects but not large ones, so == works for small values and fails for large ones. This has caused production bugs in banking and financial systems.
  Null pointer exceptions: Integer x = null; int y = x; throws NullPointerException at the unboxing. Primitives cannot be null but their boxed counterparts can, creating a mismatch that the type system cannot prevent.
  Collections cannot hold primitives: List<int> is illegal, you must use List<Integer>. This forces autoboxing on every add and unboxing on every get, with performance and correctness implications.
  Two equality systems: == compares identity for objects but value for primitives. The same operator means different things depending on whether autoboxing occurred.

C# has value types (struct) versus reference types (class). While more consistent than Java, it still has boxing when value types are stored in object references, and subtle differences in equality and copying semantics.

C++ has primitive types that do not participate in the type hierarchy. You cannot put an int in a container of polymorphic objects without wrapping it. Templates help but create code bloat.

Go has no classes and no inheritance, so the primitive/object distinction is less painful, but you still cannot define methods on built-in types or treat int and string polymorphically.

EK9'S UNIFIED OBJECT MODEL

In EK9, every value is an object with:

  Operators: ==, <>, <, >, <=, >=, <=>, $, #?, ? all work on every type
  Methods: every type has a consistent API discoverable via 'ek9 -h TypeName'
  Tri-state semantics: every object can be set, unset, or absent (see Q29)
  Polymorphism: Integer, Float, String can all be stored in collections, passed as Any, dispatched on

There is no == that sometimes compares values and sometimes compares identity. There is no autoboxing that silently converts between representations. There is no NullPointerException from unboxing a null wrapper.

  count <- 42
  ratio <- 3.14
  ready <- true
  letter <- 'A'

All four are objects. All four support the same operator patterns. All four can be put in a List of Any. All four have consistent tri-state behaviour with the ? operator.

PERFORMANCE

You might ask: are objects slower than primitives? At the language level, EK9 optimises behind the scenes. The compiler and runtime are free to use the most efficient representation internally. What matters is that the developer never sees inconsistency. The unified model means fewer bugs, simpler reasoning, and no surprise behaviour from autoboxing or identity comparison.

See also Q23 (What basic types does EK9 have?) for the full type catalog. See Q93 for defining classes.

See Q23 for basic types. See Q7 for managing dependencies.

Example

defines module qa.no.primitives

  defines program
    NoPrimitivesDemo()
      stdout <- Stdout()

      // All values are objects - no primitives
      count as Integer = 42
      ratio <- 3.14
      ready <- true
      letter <- 'A'

      // All support the same operator patterns
      stdout.println(`Count is set: ${count?}`)
      stdout.println(`Ratio is set: ${ratio?}`)
      stdout.println(`Ready is set: ${ready?}`)
      stdout.println(`Letter is set: ${letter?}`)

      // All can go in a heterogeneous collection
      items <- [count, ratio, ready, letter]
      for listItem in items
        stdout.println(`Item: ${listItem?}`)

      // All support $ for string conversion
      stdout.println(`${count} ${ratio} ${ready} ${letter}`)

Common mistakes

E50010 — int, float, char, bool do not exist as primitives.

Incorrect:

count as int = 42

Correct:

count as Integer = 42
Other ways to ask this
  • How do I use primitives instead of objects in EK9?
  • Does EK9 have autoboxing?
  • Are Integer and Float objects or primitives in EK9?
  • Why is everything an object in EK9?

Coming from another language?

Java: int/Integer dual system, autoboxing caches 127, == means different things, NPE from unboxing null. C#: value types box when stored in object references, struct vs class semantics. C++: primitives outside type hierarchy, cannot be polymorphic. Go: no methods on built-in types. Python: everything is an object (closest to EK9). Ruby: everything is an object. Smalltalk: everything is an object. EK9: unified object model, no primitives, no autoboxing, no dual equality, consistent operators on all types.

Keywords: unified, primitive, unboxing, boxing, integer, value, beginner, int, primitives, intro, wrapper, migrate, reference, start, object, autoboxing, first