[HN Gopher] Zig Error Patterns
___________________________________________________________________
Zig Error Patterns
Author : Bogdanp
Score : 111 points
Date : 2025-08-06 15:03 UTC (7 hours ago)
(HTM) web link (glfmn.io)
(TXT) w3m dump (glfmn.io)
| ijustlovemath wrote:
| The website design is so pleasing, props!
| etyp wrote:
| This goes to show how Zig's language design makes everything look
| nicer and simpler - the `errdefer` patterns in tests are super
| nice! I've debugged my Zig tests with simple print debugging (or
| try to narrow it down to a standalone case I can use a debugger),
| but I'll certainly use some of these tricks in the future.
| ww520 wrote:
| These are excellent tips. I especially like the debugger
| integration in build.zig. I used to grep the cache directory to
| find the exe. The integration avoids all the extra steps.
| skrebbel wrote:
| Wow, errdefer sounds like the kind of thing every language ought
| to have.
| jiehong wrote:
| Nice Font! (Berkeley Mono)
| vrnvu wrote:
| It's amazing how coherent Zig's fundamental building blocks are
| as a programming language, everything fits together like a
| puzzle.
|
| This post reminds me of one of Andrew's early talks about the
| type system and the comptime... With the core building blocks, we
| can achieve elegant solutions without adding unnecessary syntax
| complexity.
| davidkunz wrote:
| A little bit unrelated, but how do people deal with the
| abstinence of payloads in zig errors? For example, when parsing a
| JSON string, the error `UnexpectedToken` is not very helpful. Are
| libraries typically designed to accept an optional input to store
| potential errors?
| maleldil wrote:
| > Are libraries typically designed to accept an optional input
| to store potential errors?
|
| Yes. Stdlib's JSON module has a separate diagnostics object
| [1]. IMO, this is the weakest part of Zig's error handling
| story, although the reasons for this are understandable.
|
| [1]
| https://ziglang.org/documentation/master/std/#std.json.Scann...
| AndyKelley wrote:
| I'd like to note that std.json, as it currently stands, is
| not a good example of proper error handling. Unless you use
| that awkward lower level Scanner API, if you get a schema
| mismatch it reports some failure code and does not populate a
| diagnostics struct, which is painful and useless.
|
| On the other hand the std.zon author did not make this
| mistake, i.e. `std.zon.parse.fromSlice` takes an optional
| Diagnostics struct which gives you all the information you
| need (including a handy format method for printing human
| readable messages).
| dnautics wrote:
| I presume sometime in the not-immediate-but-not-too-
| distant-future there is going to be a push to "unify" std
| with a bunch of the "best practices" and call them out in
| the documentation.
| AndyKelley wrote:
| Indeed: https://github.com/ziglang/zig/issues/1629
| hansvm wrote:
| At a practical level, most of the language doesn't care about
| the distinction between errors and other types. You mostly just
| have to consider `try/catch/errdefer`. Your question then,
| mildly restated, is "how do people deal with cases where they
| want to use `try/catch/errdefer` but also want to return a
| payload?"
|
| It's worth asking, at least a little, how often you want that
| in the first place.
|
| Contrasting with Rust as an example, suppose you want Zig's
| "try" functionality with arbitrary payloads. Both functions
| need a compatible error type (a notable source of minor
| refactors bubbling into whole-project changes), or else you can
| accept a little more boilerplate and box everything with a
| library like `anyhow`. That's _fine_, but does it help you
| solve real problems? Opinions vary, but I think it mostly makes
| your life harder. You have stack unwinding available if you
| really need to see the source of a thing, and since the whole
| point of `try` is to bubble things up to callers who don't have
| the appropriate context to handle them, they likely don't
| really care about the metadata you're tacking on.
|
| Suppose you want Zig's "catch" functionality with arbitrary
| payloads. That's just a `union` type. If you actually expect
| callers to inspect and care about the details of each possible
| return branch, you should provide a return type allowing them
| to do stuff with that information.
|
| The odd duck out is `errdefer`. IMO it's reasonably common for
| libraries to want to do some sort of cleanup on "error"
| conditions, where that cleanup often doesn't depend on which
| error you hit, and you lose that functionality if you just
| return a union type. My usual workaround (in the few cases
| where I actually want that information returned and also have
| to do some sort of cleanup) is to have a private inner function
| and a public outer function. The inner function has some sort
| of `out` parameter where it sticks that unioned metadata. The
| outer function executes the code which might have to be cleaned
| up on errors, calls the inner function, and figures out what to
| do from there. Result location semantics make it as efficient
| as hand-rolled code for release builds. Not everything fits
| into that paradigm, but the exceptions are rare enough that the
| extra boilerplate really isn't bad on average (especially when
| comparing to an already very verbose language).
|
| Depending on the API, your proposal of having a dedicated `out`
| parameter exposed further up the chain to callers might be
| appropriate. I'm sure somebody has done so.
|
| Something I also do in a fair amount of my code is let the
| caller specify my return type, and I'll avoid work if they
| don't request a certain payload (e.g., not adding parse failure
| line numbers if not requested). It lets you write a reasonably
| generic API without a ton of code complexity, still allowing
| callers to get the information they want.
| Ar-Curunir wrote:
| > suppose you want Zig's "try" functionality with arbitrary
| payloads. Both functions need a compatible error type (a
| notable source of minor refactors bubbling into whole-project
| changes), or else you can accept a little more boilerplate
| and box everything with a library like `anyhow`. That's
| _fine_, but does it help you solve real problems? Opinions
| vary, but I think it mostly makes your life harder.
|
| This is not true, you simply need to add a single new variant
| to the callers error type, and either a From impl or a manual
| conversion at the call site
| hansvm wrote:
| "compatible error type"
|
| Which is prone to causing propagating changes if you're not
| comfortable slapping duck tape on every conversion.
| jmull wrote:
| I think the idea is errors are for control flow. If you have
| other information to return from a function, you can just
| return it -- whether directly as the return value or through an
| "out" parameter or setting it in some context.
| quantummagic wrote:
| The idiomatic way in Zig is to return the simple unadorned
| error, but return detailed error data through a pointer
| argument passed into the function, allowing the function to
| fill in extra information before returning an error.
| const MyError = error{ FileNotFound, PermissionDenied };
| fn readFile(path: []const u8, outErrInfo: *ErrorInfo) ![]const
| u8 { if (fileMissing) { if (outErrInfo)
| |info| { info.* = ErrorInfo{
| .code = MyError.FileNotFound, .message =
| "File missing", .line = @line(),
| }; } return MyError.FileNotFound;
| } return data; // success }
|
| The advantage of this is that everything is explicit, and it is
| up to the caller to arrange memory usage for error data; ie.
| the compiler does not trigger any implicit memory allocation to
| accommodate error returns. This is a fundamental element of
| Zig's design, that there are no hidden or implicit memory
| allocations
| dnautics wrote:
| should be ?*ErrorInfo in the header there =D
| metaltyphoon wrote:
| So... pretty much how C does it.
| dnautics wrote:
| I wrote an article about one possible pattern which is a
| concrete realization of your question -- though with more
| ceremony and complexity since the pathway is fully compiled out
| if you don't use it (vs a nullable pointer strategy):
|
| > Are libraries typically designed to accept an optional input
| to store potential errors?
|
| https://zig.news/ityonemo/sneaky-error-payloads-1aka
|
| if you prefer video form:
|
| https://www.youtube.com/watch?v=aFeqWWJP4LE
|
| The answer is no, libraries are not typically designed with a
| standardized convention for payload return.
| davidkunz wrote:
| Thank you all for these great and detailed explanations, I've
| learned a lot! I like the approach with an optional pointer, it
| fits to zig's philosophy quite well. Although there's a bit of
| a disconnect between the unadorned error and the corresponding
| data struct. I could imagine it requires care when the data
| struct is a union, as one needs to know which error corresponds
| to which variant.
| rkagerer wrote:
| I love the formatting and coloring on this blog page, it's
| delightful to read. Like an old school DOS game.
| sedatk wrote:
| It felt like a man page to me :)
| pyth0 wrote:
| I think the "Manual page for glfmn.io" text at the bottom
| confirms that. And I agree with the GP, it's very pleasant to
| read and look at!
| yahoozoo wrote:
| Cool stuff, but the mixed casings I see here (and have in other
| Zig code) puts me on edge (not literally but yeah). You've got
| `addSystemCommand` then a variable named `debug_step` which has a
| call `dependOn`. That said, looks like most of the stdlib stuff
| is camel case so the snake case variables are just the authors
| preference.
| schroffl wrote:
| The language reference has a section about naming identifiers
| https://ziglang.org/documentation/0.14.1/#Names
| dnautics wrote:
| there was a proposal to rid of #3, but I guess that's not
| going to happen.
___________________________________________________________________
(page generated 2025-08-06 23:00 UTC)