How do := and :=? work differently as guard operators in EK9 control flow?

← Control Flow · Ref: Q1025

The key difference is WHAT gets checked and WHEN the expression evaluates.

:= (BLIND ASSIGNMENT)

Always evaluates the expression. Always assigns. No isSet checks.

  if count := getCount() with count > 0
    process(count)

getCount() is called unconditionally. count is assigned unconditionally. The 'with' condition is the only gate.

:=? (ASSIGNMENT IF UNSET)

First checks if the target variable is already SET. Only evaluates the expression if the target is UNSET. Then checks the result before assigning.

  if cache :=? expensiveCompute() with cache > threshold
    useCache(cache)

If cache already has a value, expensiveCompute() is NEVER called. This is lazy evaluation.

WHEN TO USE :=

  - Fetching fresh data every time (sensor readings, API polls)
  - The value changes between calls and you want the latest
  - Combined with 'with' condition for validation

WHEN TO USE :=?

  - Caching: avoid re-computing if already have a result
  - Configuration defaults: try sources in priority order
  - First-match: assign from first source that provides a value
  - Loop with lazy init: only initialize on first iteration

See Q1023 for all three operators compared. See Q79 for :=? standalone. See Q1024 for ?= protection.

Example

defines module qa.controlflow.assign.vs.assignifunset

  defines function

    fetchFreshData()
      <- rtn as Integer?
      rtn: 10

    expensiveCompute()
      <- rtn as Integer?
      rtn: 99

  defines program

    AssignVsAssignIfUnsetDemo()
      stdout <- Stdout()
      minCounter <- 5
      threshold <- 50

      // === := BLIND ASSIGNMENT in IF ===
      // Always evaluates, always assigns.
      counter <- Integer()

      if counter := fetchFreshData() with counter > minCounter
        stdout.println(":= fetched and condition met: " + $counter)

      // === :=? ASSIGNMENT IF UNSET in IF ===
      // cache starts unset -> expensiveCompute() IS called
      cache <- Integer()

      if cache :=? expensiveCompute() with cache > threshold
        stdout.println(":=? computed (was unset): " + $cache)

      // cache is now set to 99 -> expensiveCompute() is NOT called again
      if cache :=? expensiveCompute() with cache > threshold
        stdout.println(":=? skipped compute (already set): " + $cache)

      // === := ALWAYS RE-FETCHES (contrast with :=?) ===
      // counter already has value 10, but := overwrites it
      if counter := fetchFreshData() with counter > minCounter
        stdout.println(":= re-fetched (always evaluates): " + $counter)

Common mistakes

E01072 — 'return' does not exist in EK9 — declare the return value with '<-' and assign it with 'rtn:'. See ek9 -h E01072 for details.

Incorrect:

      return 10

Correct:

      rtn: 10
Other ways to ask this
  • When should I use := vs :=? in a while loop or if statement?
  • What is the difference between blind assignment and assignment-if-unset in guards?
  • How does :=? avoid re-evaluation in EK9 control flow?
  • Show me := vs :=? in if and while in EK9

Coming from another language?

Java: ':=' is like simple assignment 'x = expr;'. ':=?' requires 'if (x == null || !x.isSet()) { var temp = expr(); if (temp != null && temp.isSet()) x = temp; }' — 4 lines vs 1. Go: No ':=?' equivalent. Must write 'if x == nil { x = compute() }'. Python: No direct equivalent. 'x = x or compute()' breaks for falsy values.

Keywords: control, evaluation, blind, operator, conditional, assign, if, lazy, while, flow, guard, cache, unset, compare, default