Optional (?) types
In Luma, any type can be marked as optional by prefixing it with a question mark (?). An optional type represents a value that may either hold a normal value of that type or be nil.
This makes optional types ideal for representing missing data, conversion failures, or values not yet assigned.
Declaring Optionals
maybeNum: ?int = nil
maybeText: ?str = "hello"
maybePi: ?float = 3.14
flag: ?bool = nil?intmeans “either anintornil”.?strmeans “either astrornil”.- Any type can be optional:
?int,?float,?str,?bool,?byte,?[byte], and even structs like?Person.
Assigning Values
maybe: ?int = nil
print(maybe) // nil
maybe = 42
print(maybe) // 42Only optional variables can be assigned nil. Non-optional types always require a value:
count: int = nil // errorChecking for nil
The most common pattern is to check if a value is present before using it:
maybe: ?str = nil
if maybe == nil {
print("No value yet")
} else {
print("We have a value")
}Comparisons
Optionals can be compared directly to values and to nil. The compiler handles the nil check automatically:
x: ?int = 5
print(x == 5) // true
print(x > 3) // true
print(x == nil) // false
y: ?int = nil
print(y == nil) // true
print(y == 5) // false (nil is never equal to a value)Two optionals can also be compared:
a: ?int = 5
b: ?int = 5
print(a == b) // true
c: ?int = nil
d: ?int = nil
print(c == d) // true (both nil)Pattern Matching
Use match to handle the nil and non-nil cases. The binding variable receives the unwrapped value:
result: ?int = "42".to_int()
match result {
nil -> print("parse failed")
n -> print(n) // 42
}This is the cleanest way to handle optional values — no manual nil checks needed.
Auto-dereferencing
When you assign an optional to a non-optional variable of the same base type, Luma automatically unwraps it:
x: ?int = 5
y: int = x // y is 5, automatically unwrappedThis is safe when you know the value is not nil. If the value is nil at runtime, it will panic.
Functions With Optionals
Optional return types
Functions can return optionals to indicate that the result may be absent:
fn find(name: str) -> ?int {
if name == "alice" {
return 42
}
return nil
}
result: ?int = find("alice") // 42
missing: ?int = find("bob") // nilOptional parameters
Functions can accept optional parameters:
fn greet(name: ?str) {
if name == nil {
print("Hello, stranger")
} else {
print("Hello")
}
}Conversion Methods
Many conversion methods return optionals to signal failure safely:
s: str = "123"
n: ?int = s.to_int() // 123 (as optional)
m: int = s.to_int(0) // 123 (with fallback)
bad: ?int = "abc".to_int() // nil
safe: int = "abc".to_int(0) // 0This pattern applies to to_int(), to_float(), to_bool(), to_byte(), and encoding methods like from_hex(). See Type Conversion for details.
Error Recovery With .or()
The .or() method provides a fallback value when a function call panics:
fn safe_div(a: int, b: int) -> int {
if b == 0 {
error("division by zero")
}
return a / b
}
result: int = safe_div(10, 0).or(0) // 0 (fallback)
result = safe_div(10, 2).or(0) // 5 (normal).or() can also receive a lambda with the error message:
result: int = safe_div(10, 0).or(err -> {
print("Error: ${err}")
return -1
})Optional Structs
Structs can be optional too:
struct Person {
name: str
age: int
}
friend: ?Person = nil
best: ?Person = Person("Alice", 25)Lists of Optionals
A list can hold optional elements:
scores: [?int] = [1, nil, 3]String Interpolation
String interpolation with ${} handles optionals correctly, displaying nil for absent values:
x: ?int = 42
y: ?int = nil
print("x is ${x}") // x is 42
print("y is ${y}") // y is nilQuick Reference
| Type | Example Values | Meaning |
|---|---|---|
?int |
nil, 42 |
Either no value or an integer |
?float |
nil, 3.14 |
Either no value or a float |
?str |
nil, "hello" |
Either no value or a string |
?bool |
nil, true, false |
Either no value or a boolean |
?byte |
nil, 0x41 |
Either no value or a single byte |
?[byte] |
nil, [0x48, 0x65] |
Either no value or a byte list |
?Person |
nil, Person("A", 1) |
Either no value or a struct |
Notes
- Only optional types can hold
nil— assigningnilto a non-optional is a compile error - Comparisons like
x == 5on an optional?intautomatically handle the nil case (returnsfalseif nil) matchis the recommended way to handle optionals- Auto-dereferencing works when assigning
?TtoT, but panics if the value is nil at runtime .or()catches panics fromerror()calls — it’s for error recovery, not nil coalescing