Skip to content

Errors

E# treats errors as values. In-language flow uses Result<T, E> and ?; try/catch is reserved for the BCL boundary.

Result<T, E> is a built-in type: either ok(value) or error(value). The error type is user-chosen, commonly a choice. Inspect it with .IsOk / .IsError, .Value / .Error, or the combinators .Map, .MapErr, .Bind, .Match, .Inspect, .InspectErr, .UnwrapOr, .UnwrapOrElse, .Unwrap, .UnwrapErr. The constructors ok(...) / error(...) are expressions; inside error(...) the .case dot-shorthand resolves against a known error type.

expr? unwraps a Result: it yields the ok value, or returns the error from the enclosing function. It is a real expression operator, valid anywhere an expression appears:

let user = findUser(id)? // in a let
return ok(parse(raw)? + 1) // in a return
write(parse(raw)?) // as an argument
validate(input)? // bare statement — propagate on error, else continue

The enclosing function’s error type shall be compatible with the propagated error; this is why a module tends to share one error choice. The spelling expr?.member is parsed as the null-conditional operator, not unwrap-then-access; parenthesise ((expr?).member) or bind with a let.

ToolMeaning
T?optional presence — “there might not be a value”
Result<T, E>a fallible operation — “this can fail, and here is why”

See Types → nullable.

A let … else guard binds a value or diverges; the else block shall leave the scope. Grammar and rules are in Statements.

The BCL throws; catch at that seam and translate into a Result, then return to value-based flow. There is no finally — use defer inside the try. throw with no operand rethrows and is valid only inside a catch. The three catch shapes and grammar are in Statements → try / catch / throw. The rule of thumb: catch exceptions at the edge, pass values everywhere else.