Errors
Let’s take a look at error handling. Zig has a system of error values that’s different from most other languages.
const std = @import("std");
const print = std.debug.print;
// We can define our own errors with 'error'.
const LogError = error{
InvalidBase,
InvalidNumber,
};
// A ! in the return type means the function returns
// either an error or a value.
fn logarithm(base: f64, n: f64) LogError!f64 {
if (base == 1.0)
return LogError.InvalidBase;
if (n <= 0.0)
return LogError.InvalidNumber;
return @log(n) / @log(base);
}
// The simplest way to handle errors is to return them
// to the caller with the 'try' keyword.
fn logSquared(base: f64, n: f64) LogError!f64 {
const x = try logarithm(base, n);
return x * x;
}
// The error type before the ! is optional. If it’s not
// there, the compiler will infer the type by collecting
// all errors that the function can return.
fn logCubed(base: f64, n: f64) !f64 {
const x = try logarithm(base, n);
return x * x * x;
}
// Use 'catch' to substitute a value in case of error.
fn logOrZero(base: f64, n: f64) f64 {
return logarithm(base, n) catch 0.0;
}
// We can also use a block with 'catch'.
fn logOrWarn(base: f64, n: f64) f64 {
const result = logarithm(base, n) catch |err| {
print("caught error: {}\n", .{err});
return 0.0;
};
return result;
}
// !void means the function returns either an error or
// no value at all.
fn printLogarithm(base: f64, n: f64) !void {
const result = try logarithm(base, n);
print("{}\n", .{result});
}
pub fn main() void {
// {!} is used to print either an error or a value.
print("{!}\n", .{logarithm(2.0, 1024.0)});
print("{!}\n", .{logarithm(2.0, 0.0)});
print("{!}\n", .{logSquared(2.0, 1024.0)});
print("{!}\n", .{logCubed(2.0, 1024.0)});
print("{}\n", .{logOrZero(2.0, 0.0)});
print("{}\n", .{logOrWarn(2.0, 0.0)});
// If you’re sure there won’t be an error, you can
// explicitly ignore it with `catch unreachable`.
printLogarithm(2.0, 1024.0) catch unreachable;
}$ zig run errors.zig
10
error.InvalidNumber
100
1000
0
caught error: error.InvalidNumber
0
10
Errors are only identified by their name:
const std = @import("std");
test "errors with the same name" {
const MyError = error{ A, B };
const YourError = error{ A, C };
try std.testing.expect(MyError.A == YourError.A);
// The `error.ErrorName` syntax lets us use an error
// without defining an error type.
try std.testing.expect(MyError.B == error.B);
}$ zig test error-values.zig
All 1 tests passed.
For simple error handling, return errors from main:
const std = @import("std");
fn fail() !void {
return error.FailImmediately;
}
pub fn main() !void {
try fail();
}If an error occurs, the program will print it to standard error.
$ zig run -O ReleaseSafe errmain.zig
error: FailImmediately
In Debug mode, we also get an error return trace:
$ zig run -O Debug errmain.zig
error: FailImmediately
errmain.zig:5:5: 0x11d721c in fail (errmain.zig)
return error.FailImmediately;
^
errmain.zig:10:5: 0x11d7277 in main (errmain.zig)
try fail();
^
Next example: Assertions.