The design starts from a conclusion the field converged on the hard way, from CLU through Midori to Rust, Swift, and Zig: bugs and expected errors must not share a mechanism.
A file that does not exist is a situation your program is supposed to handle. An index that is out of range is a defect in your program. Routing both through one construct means either you handle defects as though they were routine, or you ignore genuine errors as though they were impossible.
Two kingdoms
Expected errors travel on a channel declared in the signature:
type
ParseError = error (BadDigit, Overflow, EmptyInput);
function ParseDigit(c : int32) : int64 fails ParseError;
begin
if c < 0 then
fail EmptyInput;
ParseDigit := c as int64;
end;Three consumption forms cover the cases, and each is explicit at the call site:
value := ParseDigit(c) on fail leave; { propagate to my caller }
value := ParseDigit(c) on fail use -1; { substitute a default }
value := ParseDigit(c) on fail e do { inspect and branch }
Report(e)
else
value := 0;Bugs do not use this path at all. Twenty runtime failure reasons — bounds violations, checked-arithmetic overflow, failed casts — terminate the process deterministically in every optimization tier. That is the corruption floor: a defective program stops, loudly and at its source location, rather than continuing into undefined behaviour.
Out of band, by design
The channel carries no payload types. There are no sum types, no variant records, no payload enums, and no tuples in the language, so error handling does not drag a type-level construction into every signature that might fail. One error domain per function; converting between domains is an explicit re-raise.
Error return traces are available on debug and checked builds. Release pays zero — the channel compiles away to the control flow it describes.
The C boundary comes for free
An imported C function is a failing function. Its convention — negative errno, NULL return, or a sentinel value — is declared once in the interop contract and lifted at the call site into this same channel:
sock := Socket(AF_INET, SOCK_STREAM, 0) on fail use -1;No -1 comparison, no NULL check, no errno read in user code. See
the C boundary.