Quite a few people in the Zig community have rallied around the “Diagnostics pattern” where you return and handle errors per usual, but also allow the caller to interrogate a Diagnostics instance when an error happens. This contains useful information about the error, such as col/line for a parser error, or maybe an errorstring/code combination for a file error.
Depending on situation, the Diagnostics instance can be passed in to every relevant fallible function, or be part of initialization for session-oriented situations.
Here are some examples:
Clap, where you pass diagnostics as an argument: zig-clap/example/simple.zig at a4e784da8399c51d5eeb5783e6a485b960d5c1f9 · Hejsil/zig-clap · GitHub
The Scanner in std lib, where you can ask for diagnostics (if enabled) when errors occur: zig/lib/std/json/scanner.zig at 8f8f37fb0fe0ab8d98ca54ba6b51ce3d84222082 · ziglang/zig · GitHub
For my own, I used the approach in Stitch, where you can also ask for diagnostics when an error occurs in the session you have with the library: stitch/src/lib.zig at 9b82da85c70f5c702db9e4e8d498a9661c40ae5b · cryptocode/stitch · GitHub
The last one is kinda interesting in that it uses InKryption
’s idea of using pub const Diagnostic = union(std.meta.FieldEnum(MyErrorType))
to make sure error enums and diagnostics are always kept in sync. If you forget to sync up, you get a compile error. Very nice imo!