See the Zig programming language web site.
Pattern: errdefer in tests
Original: Weeknotes 2025 W38: Deep dive option
An errdefer is executed when there is an error. Assertion failures are errors, and this opens up the possibility of printing extra details that are helpful for debugging a test, but only if and when that test fails.
Here is an example — spot the errdefer disassemble(bytecode):
test "Evaluate simple" {
const src = "let x = 5;";
const bytecode = try compile(src);
errdefer disassemble(bytecode);
const result = try eval_bytecode(bytecode);
// ... assert correctness of result ...
}
This test will be intentionally quiet on success, but on error, it will print the disassembled bytecode.
Pattern: Diagnostic
A Diagnostic can be a struct with some information about where a specific error happened, what caused it, and so on.
For example, here is a Diagnostic for compiler, indicating the offset of a token where an error occurred:
const Diagnostic = struct {
pos: usize = 0,
}
A pointer to a diagnostic is passed in to the struct that will report errors:
pub const Compiler = struct {
pub fn init(str: []const u8, diag: *Diagnostic) Self {
return .{
.str = str,
.diag = diag,
};
}
// ...
}
Here is how it is instantiated:
var diagnostic: Diagnostic = .{};
Runner.init(str, &diagnostic) catch |err|
// Do something with `err` and refer to `diagnostic`
}
I found it useful to have a makeError function inside that struct, too:
pub const CompilerError = error{
UnexpectedTokenType,
UnexpectedEndOfFile,
};
pub const Compiler = struct {
// ...
fn makeError(
self: Self,
err: Error,
pos: usize,
) CompilerError {
self.diag.pos = pos;
return err;
}
}
My Zig projects
There’s TomatenMark and Deng.