Why does the compiler say my variable is unset?
← Debugging and Troubleshooting · Ref: Q251
The EK9 compiler uses flow analysis to track whether variables have been initialised. If you use a variable before giving it a value, or access a value without checking if it is set, the compiler reports an error.
COMMON ERRORS AND FIXES
E08020 - Used Before Initialised:
You declared a variable but used it before assigning a value:
count <- Integer() unset total <- count + 1 ERROR: count used before initialised
Fix: assign a value first, or use a guard:
count <- Integer() count :=? 0 total <- count + 1 now safe
E08030 - Not Checked Before Access:
You accessed a value that might be unset without a ? guard:
parsed <- Integer(someString) stdout.println($parsed) ERROR: parsed not checked
Fix: wrap in a guard:
parsed <- Integer(someString) if parsed? stdout.println($parsed) safe
E08050 - Return Not Always Initialised:
Your function has code paths where the return value is never set:
calculateArea()
-> shape as String
<- rtn as Float: Float()
if shape == "circle"
rtn: 3.14
// ERROR: rtn not set when shape is not circle
Fix: ensure ALL paths set the return value.
GUARD EXPRESSIONS
The preferred way to handle potentially unset values is with guard expressions. These combine assignment with a ? check:
if value <- computeSomething() stdout.println(`Got: ${value}`)
The block only executes if value is set. This works identically in if, switch, for, while, and try.
GUARDED ASSIGNMENT (:=?)
The :=? operator only assigns if the target is currently unset:
name <- String() name :=? "default"
This is safe because :=? checks first.
COALESCING OPERATORS
The ?? operator provides a default when a value is unset:
safe <- maybeUnset ?? "fallback"
The ?: operator evaluates a function when unset:
safe <- maybeUnset ?: getDefault
OPTIONAL AND RESULT
Optional requires a ? check before calling .get(). Result requires isOk() before .ok() and isError() before .error(). The compiler enforces these guards at compile time with no escape hatches.
DIVISION BY ZERO
Dividing by zero in EK9 does NOT throw an exception. It returns an unset value:
result <- 10 / 0 // result is unset, not an error if result? stdout.println($result) else stdout.println("Division produced unset")
See Q29 for tri-state semantics. See Q47 for Optional safe access. See Q48 for Result safe access. See Q75 for guard patterns in switch. See Q243 for coalescing operators. See Q632 for definition ordering. See Q633 for branch initialization. See Q634 for guard-based safe access.
Example
defines module qa.debugging.variableunset defines function safeDivide() as pure -> a as Integer b as Integer <- rtn <- Integer() if b <> 0 rtn: a / b findItem() as pure -> items as List of String <- rtn <- Optional() of String for item in items if item == "target" rtn: Optional(item) defines program VariableUnsetDemo() stdout <- Stdout() // Guard expression: only executes if set if result <- safeDivide(10, 3) stdout.println(`Division result: ${result}`) // Division by zero returns unset if zeroResult <- safeDivide(10, 0) stdout.println("Should not print") else stdout.println("Division by zero produced unset") // Guarded assignment with :=? name <- String() stdout.println(`Before guard: set=${name?}`) name :=? "default" stdout.println(`After guard: ${name}`) name :=? "other" stdout.println(`After second guard: ${name}`) // Coalescing operator ?? unsetVal <- String() safe <- unsetVal ?? "fallback" stdout.println(`Coalesced: ${safe}`) // Optional with guard items <- ["apple", "target", "cherry"] if found <- findItem(items) foundItem <- found.get() stdout.println(`Found: ${foundItem}`) // Integer parsing with guard parsed <- Integer("42") if parsed? stdout.println(`Parsed: ${parsed}`) badParsed <- Integer("xyz") if badParsed? stdout.println("Should not print") else stdout.println("Invalid input: integer is unset")
Common mistakes
E50060 — Stdout does not have a display() method. The correct method is println(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.
Incorrect:
stdout.display(`Before guard: set=${name?}`)
Correct:
stdout.println(`Before guard: set=${name?}`)
Other ways to ask this
- How do I fix unset variable errors in EK9?
- What does 'used before initialised' mean in EK9?
- How do I handle unset values in EK9?
Coming from another language?
Java: null by default, NullPointerException at runtime, no compile-time null tracking (unless using annotations). Python: NameError for undefined, None checks are convention not enforced. Rust: no null, Option/Result with compiler-enforced pattern matching. Go: zero values by default (0, "", nil), nil pointer panics at runtime. Kotlin: nullable types with compile-time null safety, but !! escape hatch exists. Swift: Optional with compile-time enforcement, ! force-unwrap crashes. EK9: tri-state model, compile-time flow analysis tracks initialisation, guard expressions, :=? conditional assignment, no escape hatches.
Keywords: guard, E08020, used, error-message, null-safe, unset, before, E08030, initialize, variable, analysis, debug, initialise, safe, compile, check, isset, troubleshoot, E08050, set, flow