What is the assign-if-unset operator :=? in EK9?
← Control Flow · Ref: Q79
The assign-if-unset 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 <- loadPrimary() //nothing loaded: unset name :=? "Default Name" name :=? "Second Attempt"
After these lines, name is "Default Name". The second :=? is skipped because name is already set. If the compiler can SEE the outcome it says so: straight after 'x <- String()' the target is always unset, so ':=?' always applies - write ':=' or declare it with the value (E08097); on a target it can see is set, ':=?' can never apply (E08095). A variable declared with no value ('x as String?') is the exception: filling that empty slot is what ':=?' is for.
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 two assignment operators with different guard semantics:
:= assigns unconditionally (blind assignment) :=? assigns only if TARGET is currently UNSET
The key difference: :=? checks the LEFT side (target) BEFORE assigning, and when the target is already set it neither assigns nor evaluates the right side. := always evaluates the right side and always stores it - an unset result OVERWRITES a set x. To re-point x and run a block only when the new value is set, check it explicitly: 'if x := f() then x?' (see Q1981).
IN CONTROL FLOW
The :=? operator also works in control flow guards:
if existing :=? getValue() with existing > 10 process(existing)
Here, getValue() is only called, and existing only assigned, if existing was unset. The body then runs if existing is SET - including when it already held a value and was left alone - and the 'with' condition is 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 <- loadCached() //unset when nothing is cached yet 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: ?: is the expression alternative to assign-if-unset for handling unset values. See Q286 for first-wins accumulation with assign-if-unset.
Example
#!ek9 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 <- loadPrimary() 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 <- loadPrimary() config :=? loadFallback() config :=? loadDefault() stdout.println("Config resolved to: " + config) // === ALREADY SET VARIABLE === greeting <- loadFallback() greeting :=? "Goodbye" stdout.println("Greeting stayed: " + greeting)
Common mistakes
E50050 — Using '<-' again for 'config' re-declares a variable that already exists in the scope (E50050). Declare config once, from the first source, and apply ':=?' for each later one: it only assigns while config is still unset, giving a priority chain with no early returns. See ek9 -h E50050 for details.
Incorrect:
config <- loadPrimary() config <- loadFallback() 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 the assign-if-unset operator :=? 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: :=?, assign, assign-if-unset, assignment, branch, chain, condition, conditional, control, default, fallback, fill, flow, initialize, isset, lazy, null-safe, operator, priority, safe, unset