Why does AI generate null checks instead of guards in EK9?

← What AI Gets Wrong About EK9 · Ref: Q277

AI models generate 'if result != null', 'if result is not None', or 'if result == null return' in EK9. There is no null in EK9. The keyword does not exist. EK9 uses a tri-state model (absent, unset, set) with guards.

THE AI MISTAKE

AI imports null-checking patterns from Java (null), Python (None), JavaScript (null/undefined), or Go (nil). None of these concepts exist in EK9.

THE EK9 TRI-STATE MODEL

Every EK9 object exists in one of three states:

  Absent: does not exist (missing Dict key, empty Optional)
  Present but unset: exists but has no meaningful value (String() creates unset String)
  Present and set: exists with a valid value (String("hello") is set)

THE ? OPERATOR

Check whether an object is set with the ? operator:

  if name?
    stdout.println(name)

This replaces null checks. The ? operator returns Boolean.

GUARDS

Guards combine assignment with the ? check in a single step:

  if record <- findRecord(key)
    stdout.println(`Found: ${record}`)

The guard 'if record <- findRecord(key)' declares 'record', assigns the result, and checks if it is set. If unset, the block is skipped entirely. No null check needed.

GUARDS IN OTHER CONTROL FLOW

An if, switch or try guard stands on its own. A while or for guard must be followed by 'then' and the loop's own control, and the guard runs once, before the loop:

  while value <- getPort() then count < limit
    process(value)
  for cfg <- getPort() then item in items
    process(cfg, item)
  switch port <- getPort()
    case > highPort
      useHighPort(port)

A bare 'while value <- getPort()' or 'for entry <- nextEntry()' with no 'then' is a syntax error. A switch case compares against the guarded value itself ('case > highPort'), not one of its members ('case .port > 8000' does not parse).

ASSIGN-IF-UNSET FOR DEFAULTS

The ':=?' operator assigns only if the target is unset:

  host <- String()
  host :=? "localhost"

This replaces the null coalescing pattern (Java's 'x != null ? x : default').

See Q29 for tri-state semantics. See Q74 for guards. See Q85 for guards in all control flow. See Q243 for coalescing operators. See Q281 for verifying AI code.

Example

defines module qa.ai.mistakes.nullchecks

  defines function

    findByName() as pure
      -> searchName as String
      <- found as String: String()
      if searchName == "Alice"
        found: "Alice: Engineer"

    getPort() as pure
      <- port as Integer: 8080

  defines program

    NullCheckDemo()
      stdout <- Stdout()

      // === GUARD REPLACES NULL CHECK ===
      // AI would write: result = findByName("Alice"); if (result != null) print(result)
      // EK9 way: guard

      knownName <- "Alice"
      if record <- findByName(knownName)
        stdout.println(`Found: ${record}`)

      if record <- findByName("Unknown")
        stdout.println(record)
      else
        stdout.println("Not found, guard skipped the block")

      // === ? OPERATOR REPLACES NULL CHECK ===
      // AI would write: if (name != null) print(name)
      // EK9 way: ? operator

      name <- String()
      if name?
        stdout.println(name)
      else
        stdout.println("Name is unset (not null, just unset)")

      name: findByName(knownName)
      if name?
        stdout.println(`Name is set: ${name}`)

      // === ASSIGN-IF-UNSET REPLACES NULL COALESCING ===
      // AI would write: host = host != null ? host : "localhost"
      // EK9 way: :=? assign-if-unset

      host <- String()
      host :=? "localhost"
      stdout.println(`Host: ${host}`)

      host :=? "other"
      stdout.println(`Still: ${host}`)

      // === GUARD WITH FUNCTION RETURN ===

      if port <- getPort()
        stdout.println(`Port: ${port}`)

Common mistakes

E01073 — EK9 has no null keyword. Use declaration guards with '<-' to check if a value is set and assign it in one step. See ek9 -h E01073 for details.

Incorrect:

if findByName(knownName) != null

Correct:

if record <- findByName(knownName)

E01073 — EK9 uses the ? operator to check if an object is set, not null comparisons. The ? operator returns Boolean. See ek9 -h E01073 for details.

Incorrect:

if name != null
        stdout.println(name)

Correct:

if name?
        stdout.println(name)

E01073 — EK9 uses the assign-if-unset operator :=? which only assigns if the variable is currently unset. This replaces null coalescing patterns. See ek9 -h E01073 for details.

Incorrect:

host = host != null <- host : "localhost"

Correct:

host :=? "localhost"
Other ways to ask this
  • Why does AI use 'if x != null' in EK9?
  • How do I replace AI-generated null checks in EK9?
  • What does EK9 use instead of null?

Coming from another language?

Java: null checks everywhere, NullPointerException is most common exception, Optional since Java 8. Python: 'if x is not None', AttributeError from None. JavaScript: null AND undefined, loose equality traps. Kotlin: nullable types String?, safe call ?. operator, Elvis ?:. Rust: Option<T> with match/if-let. Go: nil checks. EK9: NO null, tri-state model (absent/unset/set), ? operator checks isSet, guards combine check and assignment, :=? assign-if-unset for defaults.

Keywords: safe, mistake, isset, null-safe, hallucination, migrate, check, nil, unset, common-error, pitfall, wrong, guard, tristate, none, set, absent, ai, null