6 ms·
The error handling is so far from best-in-class that it's not even funny. Lisp conditions and restarts, which can be implemented in C, are best-in-class. Beyond
by gavinhoward 4y ago
The error handling is so far from best-in-class that it's not even funny. Lisp conditions and restarts, which can be implemented in C, are best-in-class. Beyond that, does Zig allow arbitrary values as errors now? Anything that doesn't is not best-in-class; they would be worst-in-class.
Footguns are never just relevant to beginners. I say this as something close to an expert C programmer. I hit footguns all of the time. I just don't release them because I always assume I hit them and hunt them down.
Making things easier to fuzz is table stakes; even C can do that now. I set up fuzzing for my code with a simple build option. It's not hard. It's also effective at finding test cases for you, if you use it right.
Does Zig do Design by Contract? That would make fuzzing easier. Does it do Property-Based Testing? That's a more focused version of fuzzing. In fact, it's the same to unit testing as fuzzing is to integration testing. Until Zig has Property-Based Testing, I don't believe it's making testing easier.
For that matter, as far as I know, Zig only makes unit tests easy. Unit tests are okay, but not really effective. The test suite for my code is only integration tests, which makes things easier to refactor. And yet it is effective at finding bugs.
tl;dr: What Zig has is table stakes in 2022. It is nothing special for helping people write correct code.
- Schroedingersat 4y ago> The error handling is so far from best-in-class that it's not even funny. Lisp conditions and restarts, which can be implemented in C, are best-in-class. Adding gotos is not an upgrade to readability and debugability, and comefrom was supposed to be a parody.
- gavinhoward 4y agoI suggest you read up on Lisp conditions and restarts. They are not exceptions. There is no goto other than a function call and a loop back to the spot the programmer picks. And there is no comefrom. The C equivalent would look like this: do { err = some_operation_that_can_return_errs(); } while (err != NULL && handleError(err) == ERROR_HANDLED); if (err != NULL) { panic("Could not handle error: %s\n", err_str(err)); } That is not goto or comefrom. Far from it. And of course, the panic at the end is not necessary; you could then return the error, or throw an exception, etc.
- Schroedingersat 4y agoI'm struggling to understand how your example is not just something any language with error types can do trivially. Every explanation of conditions and restarts I can find explicitly mentions searching the call stack for a handler and jumping into it, then reserving the capability to return to the function the error originated in. This is explicitly non-local and hidden control flow (ie. the much maligned goto of exceptions). The only advantage I can see asserted of having handlers (exceptions or conditions) that way is that you don't have to look for the error and pass it up manually but ! does that anyway so it doesn't seem worth throwing out your ability to reason about invariants in the context of a language with mutability and such. Then there's returning to the stack frame you started in (the part I somewhat unfairly called comefrom). You need to drag around all the machinery required for continuations in order to do this. If the goal is to have available some of the context of an outer stack frame, you could pass the relevant references down and just handle it where you wished to resume. Further thoughts: In some ways errdefer is the polar opposote of this concept. It allows the function to clean up and preserve invariants whilst passing control up the call chain.
- ghoward 4y ago> Every explanation of conditions and restarts I can find explicitly mentions searching the call stack for a handler and jumping into it, then reserving the capability to return to the function the error originated in. > This is explicitly non-local and hidden control flow (ie. the much maligned goto of exceptions). No, it's a function call. That's how you jump to code while reserving the capability to return. I mean, you explicitly "raise" an error, right? What's wrong with code like: raise AllocationFailure; desugaring to a function call? I thought function calls were local and non-hidden control flow, so why do you think otherwise? > The only advantage I can see asserted of having handlers (exceptions or conditions) that way is that you don't have to look for the error and pass it up manually but ! does that anyway so it doesn't seem worth throwing out your ability to reason about invariants in the context of a language with mutability and such. One example is that perhaps you do want to ignore certain errors, but let others through. I have code that returns an error on EOF. Usually. In reality, it calls an error handler and lets the handler decide. When I open and read files to the end, I'll encounter EOF, but I want to. So I'll push a handler onto the stack of error handlers that ignores EOF but handles all other errors. If I did that with exceptions, the call stack would be destroyed. Not good. Another example is memory allocation failure. Maybe one part of your program caches allocations and needs to be told to give them up if allocation failed. So in the handler for allocation failure, it calls a function to release those cached allocations, and when the handler returns, the allocator can try again. Hence, the restart. > You need to drag around all the machinery required for continuations in order to do this. If the goal is to have available some of the context of an outer stack frame, you could pass the relevant references down and just handle it where you wished to resume. No, you don't. If you did, I wouldn't have been able to implement it in C, but I did. [1] > Further thoughts: In some ways errdefer is the polar opposote of this concept. It allows the function to clean up and preserve invariants whilst passing control up the call chain. Passing control up the call chain on error should be a last resort because you're destroying context, but I agree, it should still exist. The cool thing is that you can do this in C too, even with cleaning up and preserving invariants. See [2], [3], and [4]. [1]: https://git.yzena.com/Yzena/Yc/src/branch/master/src/concurrency/strucon.c#L262-L325 https://git.yzena.com/Yzena/Yc/src/branch/master/src/concurr... [2]: https://git.yzena.com/Yzena/Yc/src/branch/master/src/alloc/stackpool.c#L238-L271 https://git.yzena.com/Yzena/Yc/src/branch/master/src/alloc/s... [3]: https://git.yzena.com/Yzena/Yc/src/branch/master/src/alloc/stackpool.c#L321-L360 https://git.yzena.com/Yzena/Yc/src/branch/master/src/alloc/s... [4]: https://git.yzena.com/Yzena/Yc/src/branch/master/src/alloc/stackpool.c#L790-L858 https://git.yzena.com/Yzena/Yc/src/branch/master/src/alloc/s...