How does the Optional type work in EK9?
← Getting Started · Ref: Q47
EK9 has a generic Optional type that replaces null with compiler-enforced safety. The compiler tracks whether the '?' (isSet) operator has been called and REFUSES to compile code that accesses an Optional without a proper guard. There are no escape hatches: no .unwrap(), no !!, no force-unwrap.
CREATING OPTIONALS
Two forms:
maybe <- Optional() of Integer empty, must specify type item <- Optional(42) with value, type inferred
CHECKING WITH ?
The ? operator checks if an Optional has a value:
if item? value <- item.get() safe: ? in same if block
The verbose form 'item is not empty' also works but ? is preferred.
DECLARATION GUARD
Combine creation with safety check in one expression:
if o <- getOptional() value <- o.get() safe: declaration implies ? check
The variable 'o' only exists inside the if block. If unset, the block is skipped entirely.
GET OR DEFAULT
Extract the value with a fallback:
value <- o.getOrDefault("default")
Returns the contained value if set, otherwise returns the default. No guard needed.
THINK EK9 — NOT JAVA, NOT RUST
WRONG (Java): if (opt.isPresent()) opt.get() WRONG (Kotlin): opt?.let { use(it) } ?: default() WRONG (Rust): match opt { Some(v) => v, None => default } RIGHT (EK9): if opt? then use(opt.get())
See Q84 for Optional operations (comparison, copy, merge, ternary guard). See Q85 for Optional in stream pipelines (flatten, collect). See Q74-Q78 for guard patterns in all flow controls (if, switch, for, while, try). See Q48 for the Result type (success-or-error container). See Q29 for tri-state semantics. See Q54 for Consumer and Acceptor callbacks. See Q126 for choosing the right collection type (List, Dict, Optional, Result, PriorityQueue). See Q198 for built-in generics. See Q243 for coalescing operators (??, ?:) that work with Optional values.
Use 'ek9 -h Optional' to see the full API. See Q251 for fixing unset variable compiler errors. See Q316 for how discarded Optional returns are compiler errors. See Q634 for guard-based safe access. See Q636 for chained guard access. See Q747 for migrating Swift optional binding chains to EK9.
Example
defines module qa.optional defines function <?- Returns an Optional with a value. -?> getOptional() <- rtn <- Optional("Steve") <?- Returns an empty Optional. -?> getEmptyOptional() <- rtn <- Optional() of String defines program OptionalBasics() stdout <- Stdout() // === CREATING OPTIONALS === // Empty — must specify type maybe <- Optional() of Integer stdout.println(`Empty isSet: ${maybe?}`) // With value — type inferred item <- Optional(42) stdout.println(`Item: ${item}, isSet: ${item?}`) // === DECLARATION GUARD === // Preferred: combines creation + safety check if o <- getOptional() extracted <- o.get() stdout.println(`Guard: ${extracted}`) // Empty Optional — guard block not entered if empty <- getEmptyOptional() stdout.println("Should not print") // Explicit ? check form o2 <- getOptional() if o2? checked <- o2.get() stdout.println(`Checked: ${checked}`) // === GET OR DEFAULT === name <- getOptional() assured <- name.getOrDefault("Default") stdout.println(`GetOrDefault (set): ${assured}`) emptyName <- getEmptyOptional() defaulted <- emptyName.getOrDefault("Fallback") stdout.println(`GetOrDefault (empty): ${defaulted}`)
Common mistakes
E08030 — Calling .get() on an Optional without first checking with ? triggers E08030 — has not been checked before access. The compiler enforces guard-before-access with no escape hatches. See ek9 -h E08030 for details.
Incorrect:
checked <- o2.get() stdout.println(`Checked: ${checked}`)
Correct:
if o2? checked <- o2.get() stdout.println(`Checked: ${checked}`)
E11052 — Calling a function that returns Optional but discarding the result triggers E11052 — Result or Optional result is discarded and must always be checked. You must always handle the return value. See ek9 -h E11052 for details.
Incorrect:
getOptional()
assured <- getOptional().getOrDefault("Default")
Correct:
name <- getOptional() assured <- name.getOrDefault("Default")
Other ways to ask this
- How do I handle missing values safely in EK9?
- How does EK9 prevent null pointer exceptions?
- What is Optional in EK9?
- What replaces null in EK9?
- What is the EK9 equivalent of Rust Option?
Coming from another language?
Java: Optional<T> with .get() that throws NoSuchElementException if empty, .isPresent() check not enforced by compiler, .orElse() for defaults. Python: no Optional type, uses None with 'is None' checks, no compile-time safety. Rust: Option<T> with .unwrap() that panics if None, pattern matching for safe access, compiler enforces match exhaustiveness but allows .unwrap() escape hatch. Go: no Optional, uses comma-ok idiom and nil checks. Kotlin: nullable types T? with safe call ?. and Elvis ?:, !! force-unwrap throws NPE. Swift: T? optional with if let/guard let for safe unwrapping, ?? nil coalescing for defaults, ! force-unwrap crashes at runtime (escape hatch EK9 does not allow), optional chaining with ?. for method calls. EK9: Optional of T with compiler-enforced guard patterns, .get() requires ? check in same control structure, no escape hatches, ZERO runtime crashes from Optional misuse.
Keywords: guard, safe, first, safety, isSet, compiler, unset, migrate, get, beginner, optional, start, empty, replace, nullable, nil, null-safe, none, isset, getOrDefault, npe, set, absent, enforce, null, swift, intro