Error Handling
Programs fail — files go missing, lookups miss, saves corrupt. Rasmalai treats failure as typed values flowing through declared channels: never integer codes to squint at, never exceptions arriving from nowhere.
Absence is null
A missing value is null (type Null). A type written T? holds either a T or null, so absence is visible in the type. Map lookups, find, and pop all return nullables:
let nums = [10, 20];
let first = nums.find((n) => n > 5) ?? -1;
print(first);
let missing: Int? = null;
print(missing ?? 8080);
let empty: Array<Int> = [];
print(empty.pop() ?? -1);lhs ?? rhs yields lhs when it is not null and evaluates rhs otherwise — rhs never runs when lhs is present, so fallbacks chain: a ?? b ?? 8080.
?. reaches through a chain, short-circuiting the whole thing on null:
record Profile(name: String)
record User(profile: Profile?)
let u = User(Profile("Al"));
print(u?.profile?.name ?? "anonymous");
let ghost = User(null);
print(ghost?.profile?.name ?? "anonymous");[!NOTE for TypeScript Devs]
?.looks like optional chaining and mostly behaves like it — except the chain yieldsnull(not a wrapper) when any link is missing, so the final?? defaultis the idiomatic landing. There is noOptiontype and noSome/None: write the nullable type directly (String?,Int?,Profile?).
Test for presence with != null, which narrows the binding to the payload type inside the block:
let s: String? = "hi";
if s != null {
print(s.length());
}The early-return form is guard let, which binds the payload or diverges through else:
import { Map } from "@std/collections";
fn greet(m: Map<String, Int>, key: String): Int {
guard let v = m.get(key) else {
print("missing");
return -1;
}
return v + 1;
}
fn Main(): Int {
let m = new Map<String, Int>();
m.set("ore", 7);
print(greet(m, "ore"));
print(greet(m, "dust"));
return 0;
}Failure is Result
A function that can fail returns Result<T, E>: Ok carries the value, Err carries the error. Both live in the prelude, so no import is needed:
fn div(a: Int, b: Int): Result<Int, String> {
if b == 0 {
return Result.Err("zero");
}
return Result.Ok(a / b);
}
let r = div(10, 2);
if r.isOk() {
print(r.unwrap());
}
let bad = div(1, 0);
print(bad.isErr());
print(bad.unwrapOr(-1));unwrap yields the payload and aborts on Err; unwrapOr yields the payload or the fallback. Postfix ? unwraps inside a function that itself returns a Result, returning the Err early to the caller:
fn div(a: Int, b: Int): Result<Int, String> {
if b == 0 {
return Result.Err("zero");
}
return Result.Ok(a / b);
}
fn calc(): Result<Int, String> {
let v = div(10, 2)?;
return Result.Ok(v + 1);
}
print(calc().unwrapOr(-1));? binds tighter than binary operators and never steals ternary colons. Using it on a non-Result value, or inside a function returning something else, is a compile error.
Failure is throws
A function that can fail marks throws — bare, with no error set. Callers must be inside try or declare throws themselves, and the checker enforces it:
fn load(ok: Bool): Int throws {
if ok { return 42; }
throw 0;
}
fn Main(): Int {
try {
print(load(true));
} catch (err) {
print("failed");
}
try {
print(load(false));
} catch (err) {
print("failed");
}
return 0;
}throw carries any value. A bare throw; rethrows the current error, and is legal only directly inside catch.
Handle the error at the boundary where a useful decision exists (retry, default, abort) and propagate everywhere else. Dispatch on the caught value with is checks, which narrow the binding to the tested type inside the arm:
class ParseError {
let message: String;
init(message: String) { this.message = message; }
}
fn first(): String throws {
throw new ParseError("bad header");
}
fn Main(): Int {
try {
print(first());
} catch (err) {
if err is ParseError {
print("parse problem");
} else {
print("unknown");
}
}
return 0;
}Not every function should handle what it can fail at. Declaring throws and letting the value propagate keeps intermediate layers free of plumbing they have nothing to add to.
Next: Modules and Packages — imports, the standard library tour, and the project manifest.