Do the ?? and ?: operators check for null in EK9, and what is the difference between them?

← Operators and Expressions · Ref: Q1059

EK9 has no null. Its tri-state model has THREE states - ABSENT (no object at all), present but UNSET (an object with no meaningful value, e.g. String()), and SET - and the two coalescing operators test DIFFERENT things.

?? NULL COALESCING - tests ABSENCE only:

  result <- name ?? "default"

Returns name whenever an object is present, EVEN IF IT IS UNSET. Only an ABSENT left operand gives "default". A present-but-unset value passes straight through, so the result is still unset.

?: COALESCE (ELVIS) - tests SET-NESS (absent or unset):

  result <- name ?: "default"

Returns name only if it is SET. If name is unset (or absent) the result is "default".

THE DIFFERENCE IN ONE EXAMPLE

  name <- String()              present, but UNSET
  a <- name ?? "stranger"       a is UNSET (?? saw an object, so it returned it)
  b <- name ?: "stranger"       b is "stranger"

RULE OF THUMB

To give an UNSET value a default, use ?: (or :=? to assign in place). Values you declare and assign in EK9 are present, so ?? almost never does what a default needs - it is for a left operand that may not exist at all.

The distinction from other languages:

  Java: name != null ? name : "default"     checks null
  Kotlin: name ?: "default"                  checks null
  EK9: name ?? "default"                     checks absence only
  EK9: name ?: "default"                     checks isSet

In EK9, a variable can exist but be UNSET. This is not the same as null. An unset String is a String object that has no meaningful value yet. It still exists — you can call ? on it to check.

  name <- String()                // name exists, but is UNSET
  stdout.println(`${name?}`)      // false (unset)
  name := "Alice"                 // now SET
  stdout.println(`${name?}`)      // true

If BOTH sides of ?: are unset, the result is unset.

See Q243 for all coalescing operators. See Q898 for ? is not ternary. See Q883 for tri-state semantics.

Example

defines module qa.operators.coalescingisset

  defines program

    CoalescingDemo()
      stdout <- Stdout()

      //An UNSET String: the object is present, it just has no value
      name <- String()
      stdout.println(`name is set: ${name?}`)

      //?? only tests for ABSENCE - name is present, so the unset value passes straight through
      passedThrough <- name ?? "stranger"
      stdout.println(`?? result is set: ${passedThrough?}`)

      //?: tests SET-NESS - name is unset, so the default is used
      defaulted <- name ?: "stranger"
      stdout.println(`Hello ${defaulted}`)

      //Set the name
      name := "Alice"
      stdout.println(`name is set: ${name?}`)

      //Now name is set, so both operators return it
      viaNullCoalescing <- name ?? "guest"
      stdout.println(`Hello ${viaNullCoalescing}`)
      viaElvis <- name ?: "visitor"
      stdout.println(`Hello ${viaElvis}`)

Common mistakes

E01073 — 'null' does not exist in EK9 — a variable is either set or unset (tri-state), so assign a real value rather than 'null'. See ek9 -h E01073 for details.

Incorrect:

      name := null

Correct:

      name := "Alice"
Other ways to ask this
  • Is ?? a null coalescing operator in EK9?
  • How do ?? and ?: work — are they null checks?
  • What do the coalescing operators actually check in EK9?
  • Why does name ?? "default" not give me the default when name is unset?
  • Should I use ?? or ?: to default an unset value in EK9?

Coming from another language?

Java: ternary with null check. Kotlin: ?: (elvis) checks null. JavaScript/C#/Swift: ?? (nullish coalescing) checks null. EK9: ?? checks only that an object is PRESENT (an unset value passes through); ?: checks that the value is SET - use ?: to default an unset value. EK9 has no null.

Keywords: default, coalescing, operator, null, isset, absent, elvis, unset