What is the guarded assignment operator :=? in EK9?

← Control Flow · Ref: Q79

The guarded assignment operator (:=?) assigns a value ONLY if the target variable is currently UNSET. If the variable already has a value, the assignment is silently skipped.

BASIC USAGE

Assign only when the variable has no value:

  name <- String()
  name :=? "Default Name"
  name :=? "Second Attempt"

After these lines, name is "Default Name". The second :=? is skipped because name is already set.

SETTING DEFAULTS

The most common use is providing fallback values:

  config <- loadFromFile()
  config :=? loadFromEnvironment()
  config :=? hardcodedDefault()

This creates a priority chain: file config wins, then environment, then hardcoded default. Only the first successful source is used.

DISTINCTION FROM OTHER OPERATORS

EK9 has three assignment-family operators with different guard semantics:

  :=  assigns unconditionally (blind assignment)
  :=? assigns only if TARGET is currently UNSET
  ?=  assigns only if SOURCE (RHS) is SET

The key difference: :=? checks the LEFT side (target). ?= checks the RIGHT side (source).

IN CONTROL FLOW

The :=? operator also works in control flow guards:

  if existing :=? getValue() with existing > 10
    process(existing)

Here, existing is only assigned if it was previously unset. The 'with' condition is then checked.

  try config :=? loadConfig()
    useConfig(config)

Only loads if config was unset. Useful for lazy initialization in try blocks.

LAZY INITIALIZATION PATTERN

Combine :=? with functions for lazy evaluation:

  cache <- String()
  cache :=? expensiveComputation()

The computation only runs if cache is unset. This is a clean lazy-init pattern without explicit if checks.

See Q22 for variable declaration. See Q29 for unset variables. See Q74 for guard variables in if. See Q78 for guards in try blocks. See Q104 for uninitialised properties in classes. See Q243 for coalescing operators (??, ?:) as alternatives to guarded assignment for handling unset values. See Q286 for first-wins accumulation with guarded assignment.

Example

defines module qa.flow.guard.assignment

  defines function

    loadPrimary()
      <- rtn <- String()

    loadFallback()
      <- rtn <- String()
      rtn: "fallback-value"

    loadDefault()
      <- rtn <- String()
      rtn: "hardcoded-default"

  defines program

    GuardedAssignmentDemo()
      stdout <- Stdout()

      // === BASIC :=? USAGE ===

      name <- String()
      nameStatus <- "unset"
      if name?
        nameStatus: "set"
      stdout.println("Before: name is " + nameStatus)

      name :=? "Default Name"
      stdout.println("After first :=? name is: " + name)

      name :=? "Second Attempt"
      stdout.println("After second :=? name is still: " + name)

      // === PRIORITY CHAIN PATTERN ===

      config <- String()
      config :=? loadPrimary()
      config :=? loadFallback()
      config :=? loadDefault()
      stdout.println("Config resolved to: " + config)

      // === ALREADY SET VARIABLE ===

      greeting <- loadPrimary()
      greeting :=? "Goodbye"
      stdout.println("Greeting stayed: " + greeting)

Common mistakes

E50050 — EK9 has no return statement. The guarded assignment operator :=? creates a clean priority chain pattern without needing early returns after each check. See ek9 -h E50050 for details.

Incorrect:

config <- loadPrimary()
      if config?
        return config
      config <- loadFallback()
      if config?
        return config
      config <- loadDefault()

Correct:

config :=? loadPrimary()
      config :=? loadFallback()
      config :=? loadDefault()
Other ways to ask this
  • How do I assign only if a variable is unset?
  • What does :=? do in EK9?
  • How do I set a default value for an unset variable?
  • What is conditional assignment in EK9?

Coming from another language?

Java: No direct equivalent. Must write: 'if (value == null) { value = getDefault(); }'. Verbose and error-prone. Optional.orElse() handles one level but not chains. Python: No direct equivalent. 'value = value or default' is common but broken for falsy values (0, empty string). 'if value is None: value = default' is correct but verbose. Rust: No direct equivalent. Option::get_or_insert() modifies in place but requires mut. Chaining defaults requires nested unwrap_or_else(). Go: No direct equivalent. 'if v == nil { v = getDefault() }' required. No compound operator. Kotlin: Elvis operator '?:' handles null: 'value = value ?: default'. Close but only for null, not general isSet. EK9: 'value :=? default' is a single operator that checks the target's isSet state. Works with any type, chains naturally for priority fallbacks.

Keywords: chain, unset, operator, null-safe, conditional, assignment, priority, fallback, branch, flow, lazy, initialize, safe, control, isset, default, guarded, condition