Error Handling
Zynx uses a minimal, explicit error model based on error sets and error unions.
This section describes how to declare errors, propagate them, and handle them explicitly in your code.
For a broader view of the type system, see Type System.
For async error behavior, see Async / Await.
Error Sets
An error set is a named collection of error codes.
error FileError {
NotFound,
AccessDenied,
DiskFull
}
Error Unions (T!)
An error union is a type that can hold either a value of type T or an error code.
fn open_file(path: str) -> i32! {
if (path == "") { return FileError.NotFound; }
return 1;
}
Error variants can carry an optional message string:
fn open_file(path: str) -> i32! {
if (path == "") { return FileError.NotFound("path is empty"); }
return 1;
}
The message is a str and can be retrieved in a catch (err, msg) handler.
Cancellation in async code is also expressed through T!. A canceled Future<T!> resolves to an error value (for example AsyncError.Cancelled) and must be handled explicitly with try or catch.
For details, see Async / Await.
The try Operator
Use try to propagate errors to the calling function. The caller must also return an error union.
fn process_file() -> i32! {
let fd = try open_file("data.txt");
return fd + 1;
}
The catch Operator
Use catch to handle errors locally. It provides a fallback value, an error handler block, or a handler expression.
Fallback
let fd = open_file("config.txt") catch -1;
In this form, if open_file returns an error, fd is initialized to -1.
Use this pattern when a simple, local default is acceptable.
Handler
Inside the catch block, use (err) to access the error code.
let fd = open_file("config.txt") catch (err) {
os.write("Error: {err}\n");
return -1;
};
Handler with Message
Use (err, msg) to also bind the error message string as an immutable str. When no message was attached, msg defaults to "unknown error".
fail() catch (err, msg) {
os.write("err: {err}\n");
os.write("msg: {msg}\n");
};
Captured Handler Expression
When you only need one expression, you can capture the error and return directly:
let fd = open_file("config.txt") catch (err) err_default(err);
This is equivalent to a block form but shorter.
Runtime Module Loading Errors (require)
require("...") is typed and participates in error handling. Use try or catch.
const math = try require("plugins/libmath.so");
let x = try math.add(10, 20);
If you want local recovery:
const math = require("plugins/libmath.so") catch null;
require() itself returns an error union, and catch (err) on that expression binds err: str:
const msg: str = require("plugins/libmath.so") catch (err) err;
Dynamic member access is also fallible because symbol resolution is lazy:
const math = try require("plugins/libmath.so");
let answer = math.ANSWER catch 0;
let sum = math.add(10, 20) catch 0;
When the exported ABI type is plain T, the dynamic expression behaves as T! so missing symbols can be handled through the same try / catch flow.
On dynamic loader failures, err receives the loader message captured by the runtime.
Unwrapping with if
Checking an error union in an if condition automatically unwraps the value in the then block.
let res: i32! = open_file("data.txt");
if res {
// res is now a shadowed i32 here
os.write("Success: {res}\n");
}
In the then branch, res is treated as the success value (i32 in this example).
In the else branch, res is an error from the corresponding error set.
main with Error Return
main may return void! to propagate errors out of the program entry point. If main returns an error, the runtime prints a diagnostic message to stderr and exits with code 1.
error Err {
Example
}
fn main() -> void! {
try throw();
}
Output on error:
error(Err::Example): unhandled error: `Err::Example`
Use try inside main to propagate errors from called functions, or handle them with catch to control the exit behavior manually.
Summary and Related Topics
- Use error sets to define named groups of error codes.
- Use error unions (
T!) to return either a value or an error explicitly. - Attach a message to an error variant:
ErrorSet.Variant("message"). - Use
tryto propagate errors andcatchto handle them locally. - Use
catch (err, msg)to bind both the error code and its message string. - Use
ifconditions on error unions to unwrap success values in a structured way. require("...")integrates with the sametry/catchflow.mainmay returnvoid!; unhandled errors print a diagnostic and exit with code1.
Related documentation:
- Type System – how
T!fits into the overall type system. - Control Flow – branching and pattern matching.
- Async / Await – how errors behave with
asyncfunctions. - Dynamic Modules – typed
require(...)and loader error handling.