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 <- 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 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 <- 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: ?: is the expression alternative to assign-if-unset for handling unset values (?? only replaces an absent value, not an unset one). See Q286 for first-wins accumulation with assign-if-unset.
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 <- 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), and EK9 has no return statement (E01072). Declare config once and apply ':=?' for each source: 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() 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 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: fallback, assignment, operator, initialize, assign, assign-if-unset, condition, unset, null-safe, fill, default, flow, priority, branch, :=?, chain, conditional, isset, safe, lazy, control