Types and Data
Option
Option<T> represents a value that might not be there. It is an enum with two variants: Some(T) holds a value, and None means absence. There is no nil in Lazarus: whenever something might be missing, the type is Option.
find(items: List<str>, target: str): Option<int> {
mut i = 0
for item in items {
if item == target { return Some(i) }
i += 1
}
return None
}
The standard library provides Option as std.Option. Use match to handle both cases:
match find(names, "alice") {
Some(i) => { Sys.print(f"found at {i}") }
None => { Sys.print("not found") }
}
Or use the helpers when the failure case is simple:
import std.Option
idx = find(names, "alice")
// unwrap_or returns a default when None
i = Option.unwrap_or(idx, -1)
// unwrap panics on None
i = Option.unwrap(idx)
The ? operator propagates Option out of the current function. If the value is None, the function returns None immediately. If it is Some(v), it unwraps to v:
parse_two(s: str): Option<int> {
parts = Str.split(s, ",")
a = parts.get(0)?
b = parts.get(1)?
return Some(Str.to_int(a).unwrap_or(0) + Str.to_int(b).unwrap_or(0))
}
Result
Result<T> represents either success or failure. Ok(T) holds the success value. Err(str) holds an error message:
read_config(path: str): Result<str> {
content = Sys.read_file(path)
match content {
Some(s) => { return Ok(s) }
None => { return Err(f"cannot read {path}") }
}
}
Use it the same way you use Option:
import std.Result
match read_config("settings.toml") {
Ok(cfg) => { parse(cfg) }
Err(msg) => { Sys.print(f"error: {msg}") }
}
// or with helpers
cfg = Result.unwrap_or(read_config("settings.toml"), "")
? works on Result too. A None propagates as None; an Err propagates as Err:
load_and_parse(path: str): Result<Config> {
raw = read_config(path)?
return parse_config(raw)
}
Lists
A list is an ordered sequence of values with a fixed element type. The type is written List<T>:
names: List<str> = ["alice", "bob", "carol"]
nums: List<int> = [1, 2, 3, 4, 5]
empty: List<int> = []
List indices are 0-based. The compiler adjusts them to Lua's 1-based tables automatically:
first = names[0] // "alice"
last = names[2] // "carol"
names[1] = "beth"
Common operations:
names.push("dave")
popped = names.pop() // Option<str>
found = names.get(10) // Option<str>, safe -- no panic on out of range
n = names.len()
empty = names.is_empty()
has = names.contains("alice")
Higher-order operations:
import std.Num
squares = nums.map(fn(x: int): int = x * x)
evens = nums.filter(fn(x: int): bool = x % 2 == 0)
total = nums.fold(0, fn(acc: int, x: int): int = acc + x)
first3 = nums.slice(0, 3)
joined = names.join(", ") // "alice, bob, carol"
List comprehensions build a list from another sequence:
squares = [x * x for x in nums]
big = [x for x in nums if x > 10]
Maps
A map is a key-value store. The type is written Map<K, V>:
scores: Map<str, int> = ["alice": 95, "bob": 87]
empty: Map<str, int> = [:]
Access and mutation:
s = scores.get("alice") // Option<int>
scores["carol"] = 91
scores.delete("bob")
n = scores.len()
has = scores.has("alice")
Iterating:
for name, score in scores {
Sys.print(f"{name}: {score}")
}
Map comprehensions:
doubled = [k: v * 2 for k, v in scores]
high = [k: v for k, v in scores if v >= 90]
keys = scores.keys() // List<str>
vals = scores.values() // List<int>
Custom enums
Enums are declared inside a class file. They can carry payload data in their variants:
enum Token {
Number(int),
Word(str),
Punct(str),
End
}
static describe(t: Token): str {
match t {
Number(n) => { return f"number {n}" }
Word(w) => { return f"word '{w}'" }
Punct(p) => { return f"punct '{p}'" }
End => { return "end" }
}
}
Construct a variant by name:
t1 = Number(42)
t2 = Word("hello")
t3 = End
Variants are always matched exhaustively. A match that does not cover every variant is a compile error. The _ wildcard catches anything not listed:
match t {
Number(n) => { process_number(n) }
_ => { }
}
Generics
You can write generic functions that work over any type. Type parameters go in angle brackets after the function name:
static first<T>(items: List<T>): Option<T> {
return items.get(0)
}
static zip<A, B>(as: List<A>, bs: List<B>): List<List<dynamic>> {
mut result = []
for i, a in as {
match bs.get(i) {
Some(b) => { result.push([a, b]) }
None => { }
}
}
return result
}
The standard library enums Option<T> and Result<T> use generics in exactly this way.