How do I re-point an existing variable and run a block only when the new value is set?
← Control Flow · Ref: Q1024
Assign with ':=' and check the new value with '?'. ':=' ALWAYS evaluates the right side and ALWAYS assigns the result, an unset result included; the explicit check then decides whether the body runs. What this protects is the BODY: code inside it never sees an unset value. It does not protect the target: an unset result overwrites whatever the target held before. (EK9 has no '?=' operator - it was removed; the check is always written out.)
IN IF (and WHILE): the check fits in the head
if data := fetchRecord(id) then data? process(data)
fetchRecord is called and its result is assigned to data. If data is set, the body executes; if it is unset, the body is skipped (an else branch runs if there is one). With a further condition, parenthesise it: 'then data? and (data > limit)'.
IN SWITCH (and FOR, TRY, DO): the head has no condition, so write three steps
value := parseInput(text) if value? switch value case 1 ...
If the result is unset, the entire switch (including default) is skipped.
A SET TARGET IS OVERWRITTEN
If the target already held a value and the expression returns unset, the target is unset afterwards. To keep an existing value when the new one is missing, use ':=?' (assign only if the target is unset) or assign into a NEW variable with a declaration guard ('if fresh <- fetchRecord(id)').
COMPARISON:
':=' always assigns and checks nothing.
':=' then 'x?' always assigns, then enters the body only if the result is set.
':=?' checks the target BEFORE evaluating: it assigns only if the target is unset (lazy, avoids unnecessary work), then enters only if the target is set.
See Q1023 for the operators compared. See Q759 for assigning and then checking. See Q74 for <- declaration guard.
Example
defines module qa.controlflow.repoint.flow defines function fetchSetValue() <- rtn as Integer? rtn: 42 fetchUnsetValue() <- rtn as Integer? rtn: Integer() defines program GuardedAssignmentFlowDemo() stdout <- Stdout() // === RE-POINT AND CHECK IN IF: the body only sees a SET value === existing <- Integer() if existing := fetchSetValue() then existing? stdout.println("IF re-point with set value: assigned " + $existing) // Reset for next demo existing: Integer() if existing := fetchUnsetValue() then existing? stdout.println(`IF re-point body: never printed ${existing}`) else stdout.println("IF re-point with unset value: body skipped") // === ':=' OVERWRITES A SET TARGET with an unset result === held <- fetchSetValue() if held := fetchUnsetValue() then held? stdout.println(`Held still: ${held}`) else stdout.println(`Held was 42, now set: ${held?}`) // === RE-POINT AND CHECK AROUND A SWITCH: skips the entire switch when unset === switchControl <- Integer() switchControl := fetchUnsetValue() if switchControl? switch switchControl case 42 stdout.println("Switch matched 42") default stdout.println("Switch hit default") if switchControl? stdout.println("Switch entered with value") else stdout.println("Switch skipped entirely (RHS unset)") // === RE-POINT AND CHECK AROUND A SWITCH: enters the switch when set === switchValid <- Integer() switchValid := fetchSetValue() if switchValid? switch switchValid case 42 stdout.println("Switch matched 42 from set value") default stdout.println("Switch hit default from set value")
Common mistakes
E01073 — 'null' does not exist in EK9 - assign, then check the new value with '?'. See ek9 -h E01073 for details.
Incorrect:
if existing != null
Correct:
if existing := fetchSetValue() then existing?
Other ways to ask this
- How do I assign and check isSet in an if or switch head?
- What happens when I assign an unset value in a control-flow head in EK9?
- Does ':=' overwrite a variable that is already set with an unset result?
- How do I safely assign from a function that might return unset?
- What replaced the removed ?= guard in an if or switch head?
Coming from another language?
Java: 'data = fetch(); if (data != null && data.isValid()) { switch(data) {...} }'. EK9: 'data := fetch()' then 'if data?' around the switch - or, in an if or while head, 'if data := fetch() then data?'. Rust: if let Some(v) = expr — similar concept but no assignment to an existing variable. Go: No equivalent — must check err separately.
Keywords: null-safe, switch, guarded, protect, flow, control, assignment, unset, isset, check, safe, guard, validate, if