How do I use a ?= or :=? guard in a try, in both statement and expression form?
← Control Flow · Ref: Q1358
A try accepts a guard in its header (directly after 'try'), and has an expression form that yields a value via a returning variable. If the guard leaves its variable unset, the try body is skipped and the expression yields the returning variable's initial value.
TRY EXPRESSION WITH A ?= GUARD
config as String? chosen <- try config ?= loadConfig() <- rtn as String: "default.yaml" rtn: config catch -> ex as Exception rtn: `error ${ex}`
The guarded assignment (?=) assigns loadConfig() to 'config' and checks it is SET. If unset, the body is skipped and 'chosen' is "default.yaml".
TRY STATEMENT WITH A :=? GUARD
cached as String? try cached :=? loadConfig() stdout.println(`loaded ${cached}`) catch -> ex as Exception stdout.println(`failed: ${ex}`)
Assign-if-unset (:=?) only assigns when 'cached' is currently unset, then gates the body on the result.
GUARD POSITION
The guard is the pre-flow, written right after 'try' — 'try <guard>' — before the body, resource header ('-> r <- ...') and returning variable. catch and finally are unaffected.
See Q78 for try guards. See Q1157 for try guard expressions. See Q137 for try-with-resources. See Q1361 for guards across all expression-form constructs.
Example
defines module qa.controlflow.guardtryexpression defines function loadConfig() <- rtn as String: "prod.yaml" defines program GuardTryExpressionDemo() stdout <- Stdout() // try EXPRESSION with a ?= guard (guard right after 'try') config as String? chosen <- try config ?= loadConfig() <- rtn as String: "default.yaml" rtn: config catch -> ex as Exception rtn: `error ${ex}` stdout.println(`chosen config: ${chosen}`) // try STATEMENT with a :=? assign-if-unset guard cached as String? try cached :=? loadConfig() stdout.println(`loaded ${cached}`) catch -> problem as Exception stdout.println(`failed: ${problem}`)
Common mistakes
E02001 — The guard belongs in the header directly after 'try' (try config ?= loadConfig()), not as the first statement of the body. As a guard it gates the whole body on the guard variable being set. See ek9 -h E02001.
Incorrect:
chosen <- try config ?= loadConfig() <- rtn as String: "default.yaml" rtn: config
Correct:
chosen <- try config ?= loadConfig() <- rtn as String: "default.yaml" rtn: config catch -> ex as Exception rtn: `error ${ex}`
Other ways to ask this
- Can a try/catch use a guarded assignment in its header?
- try as an expression with a guard variable
- guarded assignment with try catch
- How do I combine a guard with a try that returns a value?
Coming from another language?
Java: try is a statement, cannot bind a guarded loop-scoped variable in its header, and cannot yield a value; finally cannot set a result. You would null-check before the try. EK9: try takes a guard (<-, ?=, :=?) right after 'try', has an expression form yielding the returning variable, and the guard can skip the body entirely when unset.
Keywords: catch, exception, try, isset, expression, ?=, :=?, guard, guarded, returning, flow, control