Error Handling in Zig
Zig treats errors as values, the far superior method (subjectively). An error as a value is predictable and explicit, as opposed to the devil that is handling exceptions. It means that simply by looking at a function signature the caller can determine something can go wrong, and decide, at that point in time, how to handle the problem. Let’s look at some ways to handle errors in zig.
The ! on the return type !void is an error union. Don’t worry about what unions are quite yet, just know that if you see a ! next to the return type, the function can error and it you need to handle it. Zig gives you two ways to deal with an error response using either try or catch.
The Try
Using try will send the error back to the caller to handle.
fn someFunc() !void {
const file = try std.fs.cwd().openFile("input.txt", .{});
}
The Catch
Using catch will handle the error at the time of error, instead of sending it back to the caller. Here if there’s an error opening the file we print a message and exit.
fn someFunc() void {
const file = std.fs.cwd().openFile("input.txt", .{}) catch {
std.debug.print("file not found\n", .{});
std.process.exit(1);
};
}
The Catch Switch
catch is special because you have the option to capture the error message and define some behavior based on a condition(s). This time we capture the error message using catch and we switch on the error to create different behaviors based on the error we received.
fn someFunc() void {
const file = std.fs.cwd().openFile("input.txt", .{}) catch |err| switch (err) {
error.FileNotFound => {
std.debug.print("file not found\n", .{});
std.process.exit(1);
},
else => {
std.debug.print("you're on your own\n", .{});
std.process.exit(2);
}
};
}
The Catch Panic
Here we implement some Rust-like behaviors and choose to panic if an error occurs.
fn someFunc() void {
const file = std.fs.cwd().openFile("input.txt", .{}) catch @panic("out of memory");
}
The Catch Unreachable
unreachable has conditional and sometimes unknown behaviors depending on how your source is compiled. In safe modes you’ll get a panic, but in unsafe modes like ReleaseFast and ReleaseSmall you’ll get some undesired behavior that’s beyond the scope of this post. Just know using unreachable tells the compiler the code path should be impossible to trigger.
fn someFunc() void {
const file = std.fs.cwd().openFile("input.txt", .{}) catch unreachable;
}
The Catch Return
Finally, C-like behavior of error handling can be accomplished with Zig too, by catching the error and returning an integer.
fn someFunc() u8 {
const file = std.fs.cwd().openFile("input.txt", .{}) catch return 1;
}