Skip to content

Exception Handling

Zeus provides exception handling with try, catch, and throw for robust error management.

The Error Class

Zeus has a built-in Error class that serves as the base for all exceptions:

// Create and throw an error with name and message
throw new Error("ValidationError", "Invalid input value");

The Error class has two properties:

  • name - The type/category of the error (e.g., “ValidationError”, “NetworkError”)
  • message - A detailed description of what went wrong

Try-Catch Blocks

Handle runtime errors gracefully with try-catch:

function divide(a: i32, b: i32): i32 {
if (b == 0) {
throw new Error("DivisionError", "Division by zero");
}
return a / b;
}
try {
let result: i32 = divide(10, 0);
console.log(result);
} catch (e: Error) {
console.log("Caught " + e.name + ": " + e.message);
}

Throw Statement

Use throw to raise an exception:

function validateAge(age: i32) {
if (age < 0) {
throw new Error("ValidationError", "Age cannot be negative");
}
if (age > 150) {
throw new Error("ValidationError", "Age seems unrealistic");
}
}

Built-in Exceptions

Zeus automatically throws exceptions in certain error conditions:

NullReferenceException

Thrown when accessing a property on a null object:

class Person {
public name: string;
constructor(name: string) {
this.name = name;
}
}
let person: Person; // null by default
console.log(person.name); // Throws NullReferenceException

IndexOutOfBoundsException

Thrown when accessing an array with an invalid index:

let arr = [10, 20];
let value: i32 = arr[5]; // Throws IndexOutOfBoundsException
console.log(value);

Stack Traces

When an exception is not caught, Zeus displays a detailed stack trace:

Unhandled Exception: Division by zero
Stack Trace:
0: divide
at example.zs:3
2 | if (b == 0) {
> 3 | throw new Error("Division by zero");
4 | }
1: main
at example.zs:10
9 | try {
> 10 | let result: i32 = divide(10, 0);
11 | return result;

Catching Exceptions

The catch block receives the exception object:

try {
throw new Error("TestError", "Something went wrong");
} catch (e: Error) {
// Access the error name and message
console.log("Caught " + e.name + ": " + e.message);
}

Custom Exception Types

Subclass Error to define your own exception types. Call super(name, message) from the constructor to set the two Error properties, then throw and catch the subclass like any other type:

class NotFoundError extends Error {
constructor(what: string) {
super("NotFoundError", what + " was not found");
}
}
try {
throw new NotFoundError("user 42");
} catch (e: NotFoundError) {
console.log(e.name + ": " + e.message);
}

A catch (e: NotFoundError) matches only that type (and its subclasses); a catch (e: Error) still catches everything, since every exception derives from Error.

Multiple Catch Clauses

A try can have several catch clauses. They are tried top-to-bottom and the first whose type matches the thrown exception runs; the rest are skipped. Order specific subclasses before their base class, since a catch (e: Error) matches everything.

class NetworkError extends Error {
constructor() {
super("NetworkError", "the connection dropped");
}
}
try {
throw new NetworkError();
} catch (e: NetworkError) {
console.log("network problem: " + e.message);
} catch (e: Error) {
console.log("some other error: " + e.message);
}

If no clause matches, the exception propagates to the next enclosing try (or terminates the program as an unhandled exception).

The finally Block

A finally block runs on every exit path from the try/catch — normal completion, a caught exception, an uncaught one on its way out, and a return, break, or continue that leaves the block. Use it for cleanup that must always happen.

function run(): i32 {
try {
console.log("working");
return 1;
} finally {
console.log("cleanup always runs");
}
}
console.log(run()); // prints: working / cleanup always runs / 1

catch is optional when a finally is present — try { … } finally { … } is valid on its own. A return inside the finally overrides a pending return from the try or catch:

function pick(): i32 {
try {
return 1;
} finally {
return 2; // wins — the function returns 2
}
}
console.log(pick()); // 2

Best Practices

Descriptive Error Names

Use meaningful error names to categorize errors:

// Good - clear error category
throw new Error("ValidationError", "Invalid email format: missing @ symbol");
throw new Error("NetworkError", "Connection timeout after 30 seconds");
throw new Error("FileError", "Cannot read file: permission denied");
// Less helpful - generic name
throw new Error("Error", "Something went wrong");

Early Validation

Validate inputs early and throw descriptive errors:

function processOrder(quantity: i32, price: f64): f64 {
if (quantity <= 0) {
throw new Error("ValidationError", "Quantity must be positive");
}
if (price < 0.0) {
throw new Error("ValidationError", "Price cannot be negative");
}
return price * quantity;
}

Graceful Degradation

Handle errors gracefully when possible:

function safeGetElement(arr: i32[], index: i32): i32 {
try {
return arr[index];
} catch (e: Error) {
console.log("Caught " + e.name + ", returning default value");
return 0;
}
}

Coming Soon

FeatureStatus
Explicit rethrow (throw;)Planned