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
E08050 — A body-skipping guard ('<-', '?=', ':=?') can skip the try body entirely, and on that path the result is the returning variable's INITIAL value. Declared uninitialised ('<- rtn as String?') there would be nothing to return, so the compiler rejects it. Give the return an initial value. See ek9 -h E08050 for details.
Incorrect:
<- rtn as String?
Correct:
<- rtn as String: "default.yaml"
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: flow, control, catch, exception, try, isset, expression, ?=, :=?, guard, guarded, returning