Zig defer Patterns
A short note about some unexpected usages of Zig’s defer
statement.
This post assumes that you already know the basics about RAII, defer
and errdefer
. While
discussing the differences between them is not the point, I will allow myself one high level
comment. I don’t like defer
as a replacement for RAII: after writing Zig for some time, I am
relatively confident that humans are just not good at not forgetting defers, especially when
“optional” ownership transfer is at play (i.e, this function takes ownership of an argument, unless
an error is returned). But defer is good at discouraging RAII oriented programming. RAII encourages
binding lifetime of resources (such as memory) with lifetimes of individual domain objects (such as
a String
). But often, in pursuit of performance and small code size, you want to separate the two
concerns, and let many domain objects to share the single pool of resources. Instead of each
individual string managing its own allocation, you might want to store the contents of all related
strings into a single continuously allocated buffer. Because RAII with defer is painful, Zig
naturally pushes you towards batching your resource acquisition and release calls, such that you have
far fewer resources than objects in your program.
But, as I’ve said, this post isn’t about all that. This post is about non-resource-oriented usages
of defer
. There’s more to defer than just RAII, it’s a nice little powerful construct! This is way
to much ado already, so here come the patterns:
Asserting Post Conditions
defer
gives you poor man’s contract programming in the form of
Real life example:
Statically Enforcing Absence of Errors
This is basically peak Zig:
errdefer
runs when a function returns an error (e.g., when a try
fails). unreachable
crashes the program (in ReleaseSafe
). But comptime unreachable
straight up fails compilation
if the compiler tries to generate the corresponding runtime code. The three together ensure the
absence of error-returning paths.
Here’s an example from the standard library, the function to grow a hash map:
Logging Errors
Zig’s error handling mechanism provides only error code (a number) and an error trace. This is usually plenty to programmatically handle the error in an application and for the operator to debug a failure, but this is decidedly not enough to provide a nice report for the end user. However, if you are in a business of reporting errors to users, you are likely writing an application, and application might get away without propagating extra information about the error to the caller. Often, there’s enough context at the point where the error originates in the first place to produce a user-facing report right there.
Post Increment
Finally, defer
can be used as an i++
of sorts. For
example,
here’s how you can pop an item off a free list: