Error Handling
Errors as Values in Zig
Zig does not have exceptions. Like Go, it treats errors as values. But Zig goes further: errors are a first-class part of the type system, enforced at compile time.
Error Sets
An error set is an enumeration of possible error values:
const FileError = error{
NotFound,
PermissionDenied,
OutOfMemory,
};
You can also let the compiler infer the error set from a function's body by using ! without naming a specific set.
Error Union Types
An error union combines a normal type with an error set. The syntax is ErrorSet!ValueType:
fn divide(a: f64, b: f64) error{DivisionByZero}!f64 {
if (b == 0.0) return error.DivisionByZero;
return a / b;
}
The inferred shorthand uses just !:
fn divide(a: f64, b: f64) !f64 {
if (b == 0.0) return error.DivisionByZero;
return a / b;
}
try
The try keyword unwraps an error union. If the value is an error, it immediately returns that error from the current function:
fn doWork() !void {
const result = try divide(10.0, 0.0);
// If divide returns an error, doWork returns that same error
std.debug.print("result: {}\n", .{result});
}
try x is shorthand for x catch |err| return err. It propagates errors up the call stack without boilerplate.
Red alert! Error propagation in Zig works much like the Enterprise's alert system --- the problem is detected at the source and escalated up the chain of command until someone handles it.
catch
The catch keyword handles errors inline. You can provide a default value or run a block:
const value = divide(10.0, 0.0) catch 0.0;
// value is 0.0 if divide returned an error
With a capture:
const value = divide(10.0, 0.0) catch |err| {
std.debug.print("error: {}\n", .{err});
return;
};
Error in main
The main function can return !void to propagate errors. If an error reaches main, the program terminates with an error trace:
pub fn main() !void {
const result = try divide(10.0, 3.0);
std.debug.print("{}\n", .{result});
}
Custom Error Sets
You can define descriptive error sets for your domain:
const ValidationError = error{
TooShort,
TooLong,
InvalidCharacter,
};
fn validateUsername(name: []const u8) ValidationError!void {
if (name.len < 3) return error.TooShort;
if (name.len > 20) return error.TooLong;
}
Merging Error Sets
Error sets can be merged with ||:
const IoError = error{ ReadFailed, WriteFailed };
const ParseError = error{ InvalidFormat, UnexpectedToken };
const AppError = IoError || ParseError;
Your Task
Write a function validateAge that takes an i32 and returns error{Negative,Unrealistic}!void:
- If age is negative, return
error.Negative - If age is greater than 150, return
error.Unrealistic - Otherwise, return normally (no error)
Then write a helper function checkAge that takes an i32, calls validateAge, and prints:
"valid"if no error"age cannot be negative"if the error isNegative"age is unrealistic"if the error isUnrealistic
Each message should be followed by a newline.