Error Handling
Tape uses error returns — functions that can fail return T or Error. No exceptions, no stack unwinding, no hidden control flow.
Basic pattern
tape
@profile(t1);
import io from "io";
enum ParseErr : u8 {
BadInput = 1;
Overflow = 2;
}
fn parse_positive(val: i64) -> i64 or ParseErr {
if (val < 0) { return ParseErr.BadInput; }
if (val > 1000000) { return ParseErr.Overflow; }
return val * 2;
}
fn process(val: i64) -> i64 or ParseErr {
var doubled: i64 = parse_positive(val) or return;
return doubled + 1;
}
pub fn main() -> i32 {
var result: i64 = process(21) or return 1;
io.print_i64(result);
io.print("\n");
return 0;
}How it works
A function declares that it can fail by using or ErrorType in its return type:
tape
fn parse_positive(val: i64) -> i64 or ParseErr {
if (val < 0) { return ParseErr.BadInput; }
return val * 2;
}The caller must handle the error explicitly:
or return— propagate the error to the calleror { block }— handle the error inline
tape
// Propagate (caller must also return or Error):
var result: i64 = parse_positive(val) or return;
// Handle inline:
var result: i64 = divide(10, 0) or {
io.println("division failed");
return 1;
};
// Log the error description:
var result: i64 = parse_positive(-1) or {
io.println(ParseErr.desc(err)); // "invalid input"
return 1;
};Error enums
Errors in tape are enum values with optional description strings:
tape
enum ParseErr : u8 {
BadInput = 1: "invalid input";
Overflow = 2: "value too large";
}The description string is available at runtime via the enum’s .desc() method.
Design principles
- Zero cost on success — just a tag check on the return value
- Visible at every call site — no hidden
throwthat skips your cleanup - Composable —
or returnchains naturally through call stacks - No exceptions — no stack unwinding, no catch blocks, no surprise control flow
Last modified: