Errors
Throwing
Only objects extending Error can be thrown.
throw RuntimeError("failed");
Throw errors when the caller can reasonably recover or report a domain-specific failure. Use return values for ordinary negative results such as "not found" when absence is expected.
Catching
try {
risky();
} catch (IOError e) {
io.println("io failed: " + e.message);
} catch (Error e) {
io.println("unexpected: " + e.message);
} finally {
cleanup();
}
finally runs after the try/catch path completes.
Catch clauses are checked in order. Put specific error classes before broader
base classes. catch (Error e) acts as a catch-all for any throwable error.
An untyped catch { ... } block also catches everything but does not bind the
error value.
Geblang converts recoverable host/runtime failures into language-level
exceptions. For example, file and network failures such as a missing file,
connection refusal, or trying to bind a port that is already in use are raised
as IOError and can be caught. Unrecoverable interpreter and bytecode integrity
failures still abort execution because they indicate an implementation or
corrupt-program problem rather than an application error.
import io;
import net;
let listener = net.listenTcp("127.0.0.1:0");
let addr = net.localAddr(listener);
try {
let other = net.listenTcp(addr);
net.close(other);
} catch (IOError e) {
io.println("could not bind: " + e.message);
} finally {
net.close(listener);
}
Custom Error Classes
Define application error classes by extending Error or any built-in error
subclass:
class AppError extends Error {}
class NetworkError extends RuntimeError {}
try {
throw AppError("something failed");
} catch (NetworkError e) {
io.println("network: " + e.message);
} catch (AppError e) {
io.println("app: " + e.message);
} catch (Error e) {
io.println("other: " + e.message);
}
User error classes take an optional string message argument:
throw AppError(); # no message
throw AppError("connection refused"); # with message
Caught error values expose class and message fields:
} catch (AppError e) {
io.println(e.class + ": " + e.message);
}
Error class inheritance is fully hierarchical. A class extending RuntimeError
is also caught by catch (Error e):
class DatabaseError extends RuntimeError {}
class QueryError extends DatabaseError {}
try {
throw QueryError("syntax error near SELECT");
} catch (DatabaseError e) {
io.println("db problem: " + e.message);
}
Custom fields on error classes
Error classes can declare fields and a constructor. The constructor calls
parent(msg) to set the error message and then sets any custom fields on
this:
class HttpError extends RuntimeError {
int code;
func HttpError(int code, string msg) {
parent(msg);
this.code = code;
}
}
try {
throw HttpError(404, "page not found");
} catch (HttpError e) {
io.println(e.code as string); # 404
io.println(e.message); # page not found
}
Custom fields are accessible on the caught error by name just like message
and class.
Built-In Error Classes
| Class | Description |
|---|---|
Error |
Base class for all errors |
RuntimeError |
General runtime failures |
TypeError |
Type mismatch errors |
ValueError |
Invalid value errors |
IOError |
File and network I/O errors |
ParseError |
Parsing failures |
MatchError |
Non-exhaustive match |
The errors Module
import errors;
# Check error class hierarchy
let err = AppError("bad input");
io.println(errors.class(err)); # AppError
io.println(errors.message(err)); # bad input
io.println(errors.is(err, "Error")); # true
io.println(errors.is(err, "TypeError")); # false
# Create an error from a class name string
let dyn = errors.new("AppError", "dynamic");
# Wrap an error with additional context
let wrapped = errors.wrap(err, "request failed");
io.println(wrapped.message); # request failed: bad input
errors.is performs a full hierarchy-aware check. It traverses user-defined
class chains as well as the built-in error hierarchy.
Structured stack traces
Caught errors keep stack information. Use errors.stackTrace(e) or
e.stackTrace() to get an errors.StackTrace value instead of parsing the
formatted uncaught-error text yourself:
import errors;
import io;
func inner(): void {
throw RuntimeError("boom");
}
func outer(): void {
inner();
}
try {
outer();
} catch (RuntimeError e) {
let trace = e.stackTrace();
let first = trace.first();
io.println(errors.hasStackTrace(e)); # true
io.println(trace.length() > 0); # true
io.println(first.function()); # inner
io.println(first.line() > 0); # true
}
errors.StackTrace methods:
| Method | Returns | Description |
|---|---|---|
frames() |
list<errors.Frame> |
Structured frame values |
length() |
int |
Number of frames |
first() |
`errors.Frame | null` |
toList() |
list<dict> |
Plain dictionaries with function and line |
toString() |
string |
Original formatted stack text |
errors.Frame methods:
| Method | Returns | Description |
|---|---|---|
function() |
string |
Function name, or <top level> |
line() |
int |
Source line when available, otherwise 0 |
toDict() |
dict |
{function, line} |
toString() |
string |
Human-readable single frame |
For dictionary-oriented code, errors.frames(e) is shorthand for
errors.stackTrace(e).toList().
Runtime Failures
Most evaluator runtime failures, such as invalid operations, unknown fields,
bad argument types, and division by zero, are raised as catchable
RuntimeError exceptions. Parse, semantic, startup, and host-level internal
failures are reported directly because the script has not reached a recoverable
runtime point.
Stack Traces
Uncaught runtime errors include source-aware stack information when available.
Caught errors expose the same information through errors.StackTrace and
errors.Frame, which is more stable for logging, testing, and tooling than
parsing the displayed uncaught-error output.
When you convert low-level failures into domain errors, preserve the useful message:
func loadProfile(string path): dict<string, any> {
try {
return json.parse(io.readText(path));
} catch (Error e) {
throw RuntimeError("could not load profile " + path + ": " + e.message);
}
}