Error handling
Pluvial’s philosophy splits errors in two: mistakes that can be caught statically are
compile errors (exit 65), while value errors that cannot be known statically — integer
division by zero, stack overflow — are runtime errors (exit 70). Fallible operations are
modeled with the Result type, so that failure is a value you cannot silently ignore.
The Result type
Section titled “The Result type”Result<T> is either an ok holding a T or an err holding a string message.
Result<T, E> lets the error be a struct instead of a string. See
Types — Result for the type-level details and the
ok / err constructors.
Result<int> r = to_int("42")Constructing results
Section titled “Constructing results”ok(expr)— a success carryingexpr.err("msg")— a failure with a string message;Tis inferred from context.err(StructInstance)— a failure carrying a struct, forResult<T, E>.
def safe_div(int a, int b) Result<int> { if (b == 0) { return err("division by zero") } return ok(a / b)}Unwrapping: narrow before you read
Section titled “Unwrapping: narrow before you read”A Result is not a bool — if (r) { } is a compile error. You must check its state
with is ok / is err and only then read .value (the success value) or .error:
Result<int> r = safe_div(10, 2)if (r is ok) { println(r.value) # 5} else { println(r.error)}.valueis accessible only whenris narrowed took;.erroronly when narrowed toerr. Accessing them without narrowing — or on the wrong branch — is a compile error.- There is no implicit unwrap, consistent with the no-implicit-conversions rule.
r.error’s static type follows the Result shape:stringforResult<T>, the structEforResult<T, E>(sor.error.fieldworks).
Narrowing uses the same flow-typing machinery as nullable values, and a Result is
narrowed on both branches. See
Control flow — Flow typing.
Unwrapping with match
Section titled “Unwrapping with match”match is the natural way to peel a Result, with exhaustiveness checked at compile time:
match r { ok(v) => { println(v) } # v : T, read-only err(e) => { println(e) } # e : string (or the struct E)}Forgetting the err arm is a compile error — failure cannot be silently dropped.
Null coalescing
Section titled “Null coalescing”The ?? operator supplies a fallback for a nullable value, short-circuiting when the
left-hand side is non-null:
int? maybe = m["key"]int value = maybe ?? 0 # T? ?? T → TThe error-reporting experience
Section titled “The error-reporting experience”Every error — compile (65) or runtime (70) — is printed as the same Rust/Elm-style block:
error: cannot apply '+' to int and float --> main.plu:4:13 | 4 | int x = a + b | ^ | = help: convert explicitly, e.g. to_float(intExpr) + floatExprThe block contains a heading (error: or runtime error:), a --> file:line:col line, a
right-aligned gutter with the source line, a caret span under the offending token, and — for
four specific error kinds — a = help: suggestion. When stderr is a TTY the block is
colored; otherwise output is byte-identical to the uncolored form.
The four = help: suggestions
Section titled “The four = help: suggestions”A = help: line is attached only to the four error kinds that map one-to-one onto
Pluvial’s safety rules, each pointing to the way to satisfy the constraint:
- Mixed int/float arithmetic (no implicit promotion):
= help: convert explicitly, e.g. to_float(intExpr) <op> floatExpr - Using a nullable before narrowing:
= help: check it first, e.g. if (<var> != null) { ... } - Accessing a Result’s contents before narrowing:
= help: check it first, e.g. if (<var> is ok) { <var>.value } else { <var>.error } - Assigning a fallible conversion to a bare
T(e.g.to_int(string) -> Result<int>into anint):= help: this value can fail; receive it as Result<T> and unwrap with if (r is ok) { r.value }
Suggestions follow a strict accuracy rule: a = help: line is shown only when there is a
single correct fix. Syntax errors, undeclared names, arity mismatches, missing returns,
duplicate declarations, division by zero, and stack overflow get the plain error block with
no suggestion — when the fix is ambiguous, silence is the correct answer.
”Did you mean?”
Section titled “”Did you mean?””For typos in a name, field, method, module export, or built-in, the compiler computes the closest known name (within an edit distance of 3) and appends it:
= help: did you mean: to_upper?For example, to_uper → to_upper, nul → null, or a misspelled field name.