How do Result guard patterns and ternary access work?
← Getting Started · Ref: Q86
In EK9, Result guard patterns let you safely access ok and error values through compiler-enforced checks. The key insight is that isOk() and isError() are INDEPENDENT guards: isOk() unlocks .ok(), isError() unlocks .error(), and neither unlocks the other.
DUAL GUARD PATTERN
Check both sides of a Result independently using if/else if:
if r.isOk() okVal <- r.ok() else if r.isError() errVal <- r.error()
The compiler tracks which guard is active. Inside the isOk() branch, only .ok() is permitted. Inside the isError() branch, only .error() is permitted. Using the wrong accessor is a compile error.
? OPERATOR GUARDS OK ONLY
The ? operator is shorthand for isOk(), so it only unlocks .ok():
if r? okVal <- r.ok()
You cannot call .error() inside a ? guard block. For error access, you must use isError() explicitly.
TERNARY GUARD PATTERNS
Get a value or a default in a single expression:
okVal <- r.isOk() <- r.ok() else String() errVal <- r.isError() <- r.error() else Integer()
The ternary reads as: declare okVal, check isOk(), if true assign r.ok(), otherwise assign the default. This is concise and safe. The compiler verifies the guard matches the accessor.
AND LOGIC WITH MULTIPLE RESULTS
Combine guards across multiple Results with 'and':
if r1? and r2.isError() okVal <- r1.ok() errVal <- r2.error()
Each guard independently unlocks its own Result. The block only runs if ALL conditions pass.
NO REASSIGNMENT IN SAFE BLOCKS
Once a Result is guarded, the compiler prevents reassignment inside the safe block:
if r? r: otherResult COMPILER ERROR: NO_REASSIGNMENT_WITHIN_SAFE_ACCESS
This prevents invalidating the guard check by swapping the Result for one that might not be ok.
See Q48 for Result basics and creation patterns. See Q87 for Result operations (merge, copy, callbacks). See Q74 for guard patterns in if statements. See Q139 for when to use Result vs try/catch exception handling. See Q259 for how EK9's four-state Result differs from other languages.
Example
defines module qa.result.guards defines function <?- Returns a Result with ok value set. -?> getOkResult() <- rtn <- Result("Success", Integer()) <?- Returns a Result with error value set. -?> getErrorResult() <- rtn <- Result(String(), 42) <?- Returns a Result with both ok and error set. -?> getBothResult() <- rtn <- Result("Partial", -1) defines program ResultGuardDemo() stdout <- Stdout() // === DUAL GUARD PATTERN === r1 <- getOkResult() if r1.isOk() stdout.println(`Dual ok: ${r1.ok()}`) else if r1.isError() stdout.println(`Dual error: ${r1.error()}`) r2 <- getErrorResult() if r2.isOk() stdout.println(`Should not print`) else if r2.isError() stdout.println(`Dual error side: ${r2.error()}`) // === ? OPERATOR GUARDS OK ONLY === r3 <- getOkResult() if r3? stdout.println(`? guard ok: ${r3.ok()}`) r4 <- getErrorResult() if ~r4? stdout.println("Error result: ? is false as expected") // === isOk GUARDS ok, isError GUARDS error — INDEPENDENT === r5 <- getBothResult() if r5.isOk() stdout.println(`Both has ok: ${r5.ok()}`) if r5.isError() stdout.println(`Both has error: ${r5.error()}`) // === TERNARY GUARD: ok with default === r6 <- getOkResult() okVal <- r6.isOk() <- r6.ok() else String() stdout.println(`Ternary ok: ${okVal}`) // === TERNARY GUARD: error with default === r7 <- getErrorResult() errVal <- r7.isError() <- r7.error() else Integer() stdout.println(`Ternary error: ${errVal}`) // Ternary on empty Result falls back to default r8 <- Result() of (String, Integer) fallback <- r8.isOk() <- r8.ok() else "default" stdout.println(`Ternary fallback: ${fallback}`) // === AND LOGIC WITH MULTIPLE RESULTS === rOk <- getOkResult() rErr <- getErrorResult() if rOk? and rErr.isError() stdout.println(`Combined: ok=${rOk.ok()}, error=${rErr.error()}`) // Both-value Result: can guard both independently rBoth <- getBothResult() if rBoth.isOk() and rBoth.isError() stdout.println(`Both sides: ${rBoth.ok()} with ${rBoth.error()}`) // === SAFE BLOCK SCOPE === // Inside a guard block, the guarded Result cannot be reassigned. // This prevents invalidating the safety guarantee. // Attempting: r: otherResult inside if r? would produce // COMPILER ERROR: NO_REASSIGNMENT_WITHIN_SAFE_ACCESS r9 <- getOkResult() if r9? safeOk <- r9.ok() stdout.println(`Safe access: ${safeOk}`) // r9 is protected here — no reassignment allowed
Common mistakes
E08030 — Calling .ok() without an isOk() or ? guard triggers E08030 — has not been checked before access. The compiler enforces that isOk() must be checked before .ok(), and isError() before .error(). See ek9 -h E08030 for details.
Incorrect:
stdout.println(`? guard ok: ${r3.ok()}`)
Correct:
if r3? stdout.println(`? guard ok: ${r3.ok()}`)
E08040 — Reassigning a Result variable inside a guard block triggers E08040 — reassignment/mutation within safe access scope is not allowed. The guard verified r9 was ok; reassigning invalidates that check. See ek9 -h E08040 for details.
Incorrect:
if r9? r9 := getErrorResult() safeOk <- r9.ok()
Correct:
if r9? safeOk <- r9.ok()
Other ways to ask this
- How does the dual guard pattern work with Result isOk and isError?
- How do I use ternary Result access to get ok or error values with defaults?
- What are the safe access patterns for Result values in EK9?
Coming from another language?
Java: try-catch blocks handle errors but have no compile-time enforcement that catch blocks are present for unchecked exceptions, no safe accessor pattern, can forget to handle the error path entirely. Rust: match on Result<T,E> with Ok(v)/Err(e) arms provides exhaustive checking, but .unwrap() is an escape hatch that panics at runtime, no independent ok+error access since Result is strictly either/or. Go: comma-ok idiom 'val, err := doThing(); if err != nil' is convention not enforcement, error can be silently ignored with _, no compiler-enforced safe access. EK9: isOk() and isError() are independent compiler-enforced guards, no escape hatches, ternary guard gives concise safe access with defaults, no reassignment rule prevents guard invalidation.
Keywords: isOk, guard, null-safe, ternary, access, first, isError, intro, pattern, safe, result, isset, reassignment, ok, beginner, dual, error, start