Great post as always, thanks!
I’ve seen all of these except this here golden nugget - peak Zig for sure Nice find!
errdefer comptime unreachable
The Post Increment can be turned into Post Decrement, which is really handy for peak
on iterators:
fn peak(self: *Iterator) ?Item {
defer self.i -= 1;
self.i += 1;
if (self.i >= self.items.len) return null;
return self.items[self.i];
}
The error logging section could mention that you can capture the error with errdefer |err|
, which enables even more descriptive log messages:
const port = port: {
errdefer |err| std.log.err("failed to read the port number: {!}", .{err});
var buf: [fmt.count("{}\n", .{maxInt(u16)})]u8 = undefined;
const len = try process.stdout.?.readAll(&buf);
break :port try fmt.parseInt(u16, buf[0 .. len -| 1], 10);
};
(errdefer
with captures is currently not mentioned explicitly in the language reference, only in the grammar, but it works and can be very useful when you need it.)
I guess we need to add that to Captures and Payloads
Whoaaaaaaaaaa, didn’t know that that’s possible, thanks!
Done, added that errdefer
gem, too.
Perhaps unreachable
deserves a doc listing its uses/misuses.
Apart from the one above, I’ve seen: catch unreachable
to discard errors, unreachable;
to indicate that a block never reaches its end, else => unreachable,
to handle impossible switch
cases. Are there any other places it can pop up?
Thank you!
Good idea!
Another thing I found interesting is that assert is implemented as:
pub fn assert(ok: bool) void {
if (!ok) unreachable; // assertion failure
}
Yeah, it’s even among the langref examples, which together do a great job at illustrating how unreachable
emits a panic at runtime in Debug
and ReleaseSafe
modes (acts same as @panic("reached unreachable code")
), while comptime unreachable
results in an error at compile-time (acts same as @compileError("reached unreachable code")
).
What the langref doesn’t clarify is what happens if you do hit unreachable
in ReleaseFast
or ReleaseSmall
modes. Looks like it might be UB, not sure.
Well, I guess unreachable
does deserve its own doc here
const next_index = self.i + 1
avoids the write. Using defer to roll-back state unconditionally is error prone in most cases where you can avoid mutating state of the iterator.
Edit: This has changed - there’s a good list of undefined behavior here: Documentation - The Zig Programming Language
I’m glad you brought this up - we should open a new thread about this because as far as I know, it’s undefined behaviour.
I brought up this exact problem almost a year ago and didn’t make much ground (I’m talking about finding documentation about undefined behaviour more generally here). It’s no ones fault - there just isn’t a lot of material that I was able to find that works as an exhaustive reference for undefined behaviour (and I don’t imagine there will be one until we get closer to 1.0).
That may have changed, but imo, it’s totally reasonable. Today’s undefined behaviour (especially where it’s not as obvious as unreachable
) could change as the compiler changes - I don’t imagine they’ll spend a lot of time documenting that for us in the interim.
Anyhow, we should probably dedicate a new thread to that if we want to keep exploring the subject. Also, thanks for updating the doc!
Design by Contract
with defer + assert
is awesome!
True in this example, purposely made very simple to keep it conise. But if advancing the iterator involves mucho more than just incrementing a usize
(usually by calling a method that mutates several things,) using a defer
with a block to undo it all at once is less error-prone than manually undoing it all at each possible exit branch.
so can’t do something like this right?
errdefer comptime unreachable;
// can't do this? anymore after right?
try some_fn_may_fail();
but something like this should be allowed?
some_fn_may_fail() catch unreachable;
That is Unreachable - error discarding misuse.
Instead use some_fn_may_fail() catch @panic("message");
Or make sure that it is some_fn_never_fails_ensured_through_invariants() catch unreachable;
and ideally you would have asserts / tests that make sure these invariants work.
hmm i see, so basically errdefer comptime unreachable;
ensures that after this nothing else generates any sort of code which from the language prospective returns error? by language prospective? correct me if i am wrong, these errors are kind of doesn’t have any special meaning when compiled to machine code right, so they’re like special some sort of compiler intrinsic which tells the code generator to generate some error handling machine code based on the error type? perhaps i don’t have an idea how these errors are handled under the hood.
They are implemented with error sets, which can be thought of as special enums.
Take a look at this: Language Documentation - Error Set Type
Similar to how errdefer
can capture the error, I’m curious if people have considered what new patterns might be enabled by allowing defer to capture the return value (or block expression value for block-level defers)?
Within a function that changes some state, you could also use defers to debug the state with debug prints. Something akin to this:
fn updateState(self: *State) {
std.debug.print("before: {}\n", .{self});
defer std.debug.print("after: {}\n", .{self});
// ... code to update the state ...
}