6 ms·
Error management is different, and quite uniquely done. Zig deals with failure modes in a much more fine-grained way than Rust, which favours the algebraic-wrap
by gmfawcett 8y ago
Error management is different, and quite uniquely done. Zig deals with failure modes in a much more fine-grained way than Rust, which favours the algebraic-wrapper approach (Result / Option types). If you're writing actual, low-level systems code, your code might need to react differently depending on the failure mode: you need to know if it was an I/O issue, or out-of-memory, or disk-full, or etc. At the call point, Zig lets you enumerate all of the possible failure reasons comprehensively, and handle each one specifically if you choose to. This enumeration is produced and checked by the compiler, so you can't miss any cases. (It also lets you disregard the specifics if you don't care.)
- dbaupp 8y agoThat sounds quite similar to Rust, except less fine-grained than Rust. The Result type has an Error parameter which is usually an enumeration of the possible failure conditions, e.g. https://doc.rust-lang.org/std/io/struct.Error.html https://doc.rust-lang.org/std/io/struct.Error.html . That is, an IO function returns only IO errors, as compared to Zig's strategy which unifies all declared errors in the program into a single "error" enumeration. (To be clear, there's a lot of cases when this precision isn't so useful, but the contention here is how fine grained they can be.)
- gmfawcett 8y agoThere's definitely there's a distinction here between the approach you've described (Rust can indicate that a function returns only IO errors -- but doesn't tell you which subset of "all IO errors" might actually occur at this call point) and Zig's (the function call can result in an arbitrary set of specific errors, some of which might be IO and some might not; and only the actually possible errors are included in the set). Is one better than the other? I doubt that question has an objective, one-size-fits-all answer. I'm happy to see experimentation in this domain! My point really is just that Zig is different from Rust in how it handles errors, even if there are some superficial similarities.
- dbaupp 8y agoI think you haven't understood my point or maybe I haven't understood yours, and maybe it is because I have misunderstood Zig's approach. My understanding is that something like `fn f() -> %T` returns a T or an `error`, and the latter can be literally any error value that exists anywhere in the program. It isn't restricted to the actual possible errors that might occur inside f, and thus has the same failings of Rust that you point out (don't know which subset of errors might be returned), except the unknown set of possible errors is likely to be larger. (And, you can still get all the niceties of Rust's error handling infrastructure like the ? operator if you manually define an error type that is restricted to the exact set of possible errors: there's not a privileged type for how to represent errors.)
- AnIdiotOnTheNet 8y agoErrorSets have been in the language for some time now. The syntax you are referencing is pretty outdated now too, when considering the pace of language changes here in Zig's pre-1.0 days. https://ziglang.org/documentation/master/#Error-Set-Type https://ziglang.org/documentation/master/#Error-Set-Type
- dbaupp 8y agoAh that explains it: I was looking at the now-old https://andrewkelley.me/post/intro-to-zig.html#error-type https://andrewkelley.me/post/intro-to-zig.html#error-type
- gmfawcett 8y agoIf there's any misunderstanding, it's probably mine. :) I'm reasonably familiar with Rust, and I understand your argument; but I only read about Zig a couple weeks ago, so my knowledge there is based only on a little reading and experimentation. My reading of the release notes: https://ziglang.org/download/0.2.0/release-notes.html https://ziglang.org/download/0.2.0/release-notes.html ...is that error sets can be inferred (at both call-site and in the function declaration); and that inferred error-sets on return types are exact, in the sense that an inferred set will only include the actually-possible errors encountered in the call-tree. To be fair, I don't know whether a function can declare that it may fail with a given, explicitly-declared error-set, where that error-set includes errors that the function actually cannot produce. (E.g., I say I can throw a network-error code, but I perform no network calls.) If this is permissible (i.e, the compiler doesn't complain), then that undercuts my argument a bit. :) On the other hand (again from the release notes), most functions are encouraged to simply declare their return type as !T, where the ! represents "the inferred error-set for the function," and this seems to support my claim.
- deleted 8y ago[deleted]
- ionforce 8y agoIs there something about Rust that precludes it from reacting to failure modes as you describe?
- gmfawcett 8y agoI'm guessing, but I think the tricky part for Rust might be the automated construction of the Error-union type. E.g., at a certain call-point, the full union of all possible errors is ErrorSet<E1, E2, E3, ..., En>. The compiler determines this union, not the programmer. At the next call point, the union may be entirely different. To my best knowledge, Rust doesn't currently allow for variadic type constructors (i.e., where the number of parameters may be knowable at compile time, but the arbitrary-length parameter list cannot be expressed in the abstract syntax of the language). https://ziglang.org/documentation/master/#Errors https://ziglang.org/documentation/master/#Errors edit: when I say "full union of all possible errors", I just want to be clear that this means "all the possible errors that could occur at this call point, and no others." It's not just a bag of "all possible errors that the language/library declares to exist." edit #2: On second thought, I guess it might be possible for a Rust tool to build these types, although I think it would require changes to the language implementation. Variadic type constructors aren't necessarily needed. Another approach would be that every (type-specialized) function returns a Result<T, ES> type, where ES is some type that implements an "ErrorSet" kind, and where ES is very possibly unique to the function (that is, no other function might return Result<ES>). For example function A() might fail due only to two cases (say, out-of-memory or disk-full, but never network-error), and function B() might fail only on disk-full. Then the ErrorSet type for A has two constructors (OOM and DiskFull), where the ErrorSet type for B has only one (DiskFull). You could then use Rust's (excellent) pattern matching to exhaustively handle all cases (i.e., the specific constructors of that ErrorSet type) at the call site. So the tricks would be: 1. Instrument the language to tag each specialized function with its error-set; this is computed recursively by examining the failure modes in the function body, as well as the error-sets of every function it might itself call, and taking the union of those sets. (Side note: I believe that, in Zig, a type-specialized function maps to a single error-set -- i.e., the error-set doesn't depend on runtime arguments in any way. But I could be wrong about this.) 2. Generate a potentially new ErrorSet-ish type for each function, or more spefically, for each error-set that is generated using the process in step #1. (I suppose you would have to give the type a name, but maybe it could be an anonymous internal type if there is language support for that.) 3. Then you could naturally "match" on the function's return value, and know that you've covered (exactly) the possible failure modes. But this is just armchair-quarterbacking on my part. I don't know the internals of the Zig error system, nor what changes Rust would actually need.
- Lev1a 8y agoLet me introduce you to the concept of ErrorKind in Rust (i.e. in the IO module of the std): https://doc.rust-lang.org/std/io/enum.ErrorKind.html https://doc.rust-lang.org/std/io/enum.ErrorKind.html There is also the "failure" crate which has patterns for the kind of thing you mentioned: https://boats.gitlab.io/failure/error-errorkind.html https://boats.gitlab.io/failure/error-errorkind.html https://boats.gitlab.io/failure/custom-fail.html https://boats.gitlab.io/failure/custom-fail.html
- gmfawcett 8y agoI don't believe that either ErrorKind or 'failure' gives the same degree of automated enumeration and checking that Zig provides. I'm not a Zig apologist... I just think you should read through the Zig documentation and examine the differences for yourself. https://ziglang.org/documentation/master/#Errors https://ziglang.org/documentation/master/#Errors https://ziglang.org/download/0.2.0/release-notes.html https://ziglang.org/download/0.2.0/release-notes.html (see "Error Sets")
- deleted 8y ago[deleted]
- bpicolo 8y agoIf I'm understanding correctly, you can use enums in rust to recreate the same, though it would be slightly more verbose. enum MyErrorSet { NotFound(ErrorKind), SomethingElse(ErrorKind), } If you don't care about propagating the original ErrorKind then that's just enum MyErrorSet { NotFound, SomethingElse }
- gmfawcett 8y agoYes, I think you're right. But the burden for generating and maintaining the enums is on the developer; whereas in Zig, they can be automatically inferred by the compiler. I don't think the distinction is super important in application work (for example). But for systems code I can see the benefit of having the compiler tell you, with certainty, why a given function can fail (and why it can't). E.g., a good device driver should handle all its failure modes with care, rather than with a shotgun approach.