Error payloads in Zig
Feb 13, 2026
I do error payloads in Zig by making a union(enum) -based Diagnostics type for each function. These types have special methods which remove code bloat at call sites. A Diagnostics type can be defined inline, and the errorset can be generated inline from the Diagnostic’s enum tag.
pub fn scan ( db : * c . sqlite , diag : * diagnostics . FromUnion ( union ( enum ) { SqliteError : sqlite . ErrorPayload , OutOfMemory : void , LoadPluginsError : diagnostics . OfFunction ( transforms . loadPlugins ). ErrorPayload ( error . LoadPluginsError ), }), ): diagnostics . Error ( @TypeOf ( diag )) !void { // ... }
My diagnostics module as a gist
The generated type is a wrapper around an optional payload. It generates an error set type from the union(enum) fields.
// diagnostics.zig pub fn FromUnion ( comptime _Payload : type ) type { return struct { pub const Payload = _Payload ; pub const Error = ErrorSetFromEnum ( std . meta . FieldEnum ( Payload )); payload : ? Payload = null , // ... methods ... }; }
The first thing you will want to do is set a payload while you return an error. For this, there is the withContext method.
pub fn countRows ( alloc : std . mem . Allocator , db : * c . sqlite , opts : Options , diag : * diagnostics . FromUnion ( union ( enum ) { SqliteError : sqlite . ErrorPayload , OutOfMemory : void , }), ) !usize { const st = sqlite . prepareStmt ( alloc , db , "SELECT COUNT(*) FROM {0s} WHERE ({1s})" , .{ opts . table_name , opts . where_expr }, ) catch | err | return switch ( err ) { error . SqliteError => diag . withContext ( error . SqliteError , . init ( db )), error . OutOfMemory => error . OutOfMemory , }; // ... }
Here, sqlite.ErrorPayload.init saves 500 bytes of error message from sqlite. That payload gets saved to diag and the error is returned.
... continue reading