Errors
Zig has no exceptions. A function that can fail returns an error union,
written E!T: either an error from set E, or a T.
const std = @import("std");
const expect = std.testing.expect;
const FileOpenError = error{
AccessDenied,
OutOfMemory,
FileNotFound,
};
// Error sets coerce into supersets, so a narrow error can be returned
// from a function that declares a wider set.
const AllocationError = error{OutOfMemory};
test "error set coercion" {
const err: FileOpenError = AllocationError.OutOfMemory;
try expect(err == FileOpenError.OutOfMemory);
}
fn failingFunction() error{Oops}!void {
return error.Oops;
}
test "catch supplies a fallback" {
// `!u8` is shorthand for "some inferred error set, or u8".
const parsed = std.fmt.parseInt(u8, "not a number", 10) catch 0;
try expect(parsed == 0);
}
test "try propagates" {
// `try x` is `x catch |err| return err`.
try std.testing.expectError(error.Oops, failingFunction());
}
test "capture the error in catch" {
var captured: anyerror = undefined;
failingFunction() catch |err| {
captured = err;
};
try expect(captured == error.Oops);
}
fn parseOrDefault(text: []const u8) u32 {
// `if` can unwrap an error union too, with `else |err|`.
if (std.fmt.parseInt(u32, text, 10)) |value| {
return value;
} else |_| {
return 0;
}
}
test "if on an error union" {
try expect(parseOrDefault("42") == 42);
try expect(parseOrDefault("abc") == 0);
}Error sets are types
const FileOpenError = error{ AccessDenied, OutOfMemory, FileNotFound };
A narrow set coerces into a wider one, so a function returning
error{OutOfMemory} can be called from one returning FileOpenError without
ceremony. anyerror is the set of all errors: convenient, but it discards
exactly the information that makes this system useful.
Four ways to handle one
| Form | Meaning |
|---|---|
try x | unwrap, or return the error to my caller |
x catch fallback | unwrap, or use this value instead |
x catch |err| { ... } | unwrap, or handle the error here |
if (x) |v| ... else |err| ... | branch on both outcomes |
try is by far the most common, and it is only shorthand:
x catch |err| return err.
Why this beats exceptions
The failure paths are in the type signature and in the control flow you can
see. There is no invisible unwinding, no hidden second exit from every line.
That is the same reason defer is needed and sufficient for cleanup.