[HN Gopher] Error Handling in Zig
___________________________________________________________________
Error Handling in Zig
Author : nalgeon
Score : 142 points
Date : 2023-08-06 09:54 UTC (13 hours ago)
(HTM) web link (www.aolium.com)
(TXT) w3m dump (www.aolium.com)
| [deleted]
| ksec wrote:
| Hopefully this stayed on the front-page long enough and Andrew
| gets to reply from his perspective.
| AndyKelley wrote:
| Creating a Result as in this blog post is an anti-pattern, for
| the reasons pointed out in the article. The best pattern that is
| available today is to continue to use an error union for the
| return type, and accept a mutable "diagnostics" parameter where
| it stores extra useful information about failures.
| [deleted]
| pjmlp wrote:
| Everyone complains about checked exceptions, blames them on Java,
| while they actually came up on CLU, Mesa, Modula-3 and C++.
|
| And at the end of the day, it turns out forced error checking is
| an idea everyone keeps reinventing, because it is actually useful
| to know what errors can be "thrown".
| masklinn wrote:
| > And at the end of the day, it turns out forced error checking
| is an idea everyone keeps reinventing, because it is actually
| useful to know what errors can be "thrown".
|
| The problem of java's checked exceptions is not that being
| explicit is bad, it's that java's implementation is terrible.
| LelouBil wrote:
| Yeah, exceptions and try/catch blocks have way worse
| ergonomics that Result<Err,Ok>.
| pjmlp wrote:
| Then why does Rust need third party crates for sane
| ergonomics?
| veber-alex wrote:
| Because there isn't a one size fits all solution to error
| handling?
|
| Because before anyhow/thiserror became popular there were
| 5 other popular crates for handling errors and if Rust
| added one of those to std we would be stuck with a subpar
| solution forever?
|
| Because cargo is so easy to use that your preferred error
| handling solution is one `cargo add` command away?
|
| Because for simple cases the tools the standard library
| offers are good enough?
| pjmlp wrote:
| More like, most people rather complain than learn how to use
| them properly.
| peheje wrote:
| For my work, I usually catch general exceptions and handle
| their aftermath, like putting the error in an error queue to be
| manually fixed and replayed or showing an error page. While
| there are domains like power plants or car software where every
| error must be meticulously handled, my approach suits my
| domain.
|
| When I see code making me catch numerous unique exceptions, it
| often hints at an API design issue. A more refined design might
| encapsulate such information in a response, maybe through a
| discriminated union or an enum with a message. If I can, I'd
| refactor it to match this ideal. If not, I'd use adapters to
| convert diverse exceptions into standardized errors.
|
| Exceptions should be exceptional: situations like memory
| shortages, database disconnects, or accessing a disposed
| resource. For these unforeseen events, control flow is
| typically the same as they're unexpected and beyond my control.
| Rusky wrote:
| The hard part of checked exceptions is when they start
| interacting with higher order code- in Java this often happens
| when an interface method wants to throw. Each implementation
| may want to throw its own set of exceptions, but the code that
| just works with the interface doesn't care about any of them,
| and existing languages don't really give you a way to express
| this.
|
| What you really want here is polymorphism in the exception
| specification, and a way for consumers to allow these new
| exceptions types to "tunnel" through out to the caller that
| knows about them. And for _that_ to work while still providing
| any guarantees that exceptions are handled, it turns out you
| need something like a borrow checker, to prevent the interface
| object from escaping that outer scope!
|
| Here's a 2016 paper on the subject (with Barbara Liskov as a
| coauthor):
| https://www.cs.cornell.edu/~yizhou/papers/exceptions-
| pldi201....
| pjmlp wrote:
| Just like mixing errors in Rust requires third party packages
| and macros to sort out all boilerplate code?
| Rusky wrote:
| Not really what I'm talking about here- that issue comes up
| when you want to combine multiple concrete error types in a
| single monomorphic call site, rather than when dealing with
| polymorphism.
|
| Rust (and other languages that use something like `Result`
| for errors) handles the polymorphic case a bit better than
| Java, because you can already use the language's usual
| polymorphism support for the error type. But it's still not
| quite as smooth as first-class polymorphic checked
| exceptions would be, since the interface (or trait) and its
| callers have to do a bit more manual plumbing in some
| cases.
|
| For example, a simple `impl Fn() -> T` can be instantiated
| with `T = Result<X, E>`, but only if the caller is just
| going to return the `T` directly- otherwise the error won't
| be propagated immediately. A slightly more annoying
| situation is when you have some `I: Iterator` that can
| fail- often you fall back to `I: Iterator<Item = Result<X,
| E>>`, which is not quite right, and expect the consumer to
| stop calling `next` if it gets an `Err`.
|
| With polymorphic checked exceptions, you could use `I:
| Iterator<Item = X>`, with an additional annotation that
| `next` may fail with an `E`. Error-oblivious combinators
| like `map` or `fold` would continue to work directly with
| `X` values, but automatically propagate `E`s to the
| eventual caller that knows the concrete type of `I`.
|
| (And again, crates like anyhow/thiserror don't really
| address this problem- they're solving a different issue
| entirely.)
| hgs3 wrote:
| Perhaps a controversial opinion, but I think exceptions should
| only be thrown when something exceptional happens. Languages,
| like Java and Python, overuse them e.g. failing to open a file
| is not exceptional, but running out of memory is.
| pjmlp wrote:
| Running out of memory is an Error, not Exception, in Java's
| case, and the throw semantics are different.
| grumpyprole wrote:
| I also thought checked exceptions in Java were fantastic. They
| are a form of statically checked effects. I would like to see
| more of this sort of thing not less.
| wredue wrote:
| Inflexibility is an actual problem.
|
| In rust, before anyhow and thiserror, you'd see some pretty
| shitty hacks for the inflexible error system, such as just
| making all errors just a string.
|
| It is clear that having all the errors in a list is actually
| good now, but that doesn't stop programmers from hating writing
| boilerplate.
|
| Again, before anyhow, if you did error _properly_ , your
| errors.rs had huge swathes of From implementations. Error.rs
| boilerplate often outstripped your actual code.
|
| The complaints that it's hard to change interfaces is bad, as
| it's difficult to change interface methods regardless.
| pjmlp wrote:
| The fact that Rust requires third party crates for proper
| error handling ergonomics, proves the point it is only
| partially designed.
| wredue wrote:
| It's not partially designed so much as the type system
| demands it for rust.
|
| Very unfortunately for rust, making errors not just
| maddening boilerplate forces you to trade compile time for
| reasonable errors (although, honestly, anyhow "feels" hacks
| to me). Compile time is already a place rust struggles as
| it is.
|
| I wouldn't bank on rust style languages having any
| semblance of good ergonomics for errors. But at the same
| time "you can just ignore it" is really not great either.
|
| Zig errors are actually pretty nice to work with, but as is
| pointed out, they struggle with producing really good
| messages, or giving more information back.although, I will
| say that I nearly never need to send more information back,
| and there are patterns to help with that.
|
| Still, if there was a language concept for it, that would
| be nicer. It's actually not an easy problem for zig and the
| core foundations of the language. Just like it's not an
| easy problem for rust and its core foundations.
|
| Errors are just really shitty and, as yet, I don't think
| there exists good ergonomics. I personally haven't seen a
| language that does them well.
| p0nce wrote:
| What's the downsides? Anyone have used Zig and could report on
| the good and bad?
| nightowl_games wrote:
| Good points raised. In Go, I've struggled to attach stack traces
| to errors. I have a plan (a custom error type, and type checking
| for that type) but it's not "bulletproof". Seems similar. Seems
| like there's opportunity for some language evolution here.
| linux2647 wrote:
| IIRC, part of the language proposal to make errors wrappable in
| Go's stdlib included a stack trace. And indeed
| github.com/pkg/errors includes that. But it never made it into
| the stdlib implementation
| masklinn wrote:
| > Seems similar.
|
| It's not: because error sets are a built-in and completely
| bespoke feature, and they get handled via built-in constructs
| exclusively (try / catch), zig can actually attach / generate
| error traces: https://ziglang.org/documentation/0.3.0/#Error-
| Return-Traces
| larusso wrote:
| I tend to check the error handling capabilities of a language to
| see how expressive and easy it is to return errors. I personally
| prefer result types over optionales (e.g. the mentioned added
| context instead of a nothing). It seems that zigs shoots in the
| middle here. I like the implicit errors shown but also feel that
| the lack of context isn't great. What is missing for me is
| chaining and a simple message. That being said I'm also not super
| happy how rust manages errors. At the moment I write my tools and
| crates with ,,thiserror" and ,,anyerror" which also illustrates
| how errors most of the times are uses. I say most since it really
| depends. In libraries I tend to introduce an error type per
| bigger module. And wrap low level errors so it results in a chain
| of errors (similar how Java does it with their exceptions) This
| gives me the most context and a kind of breadcrumb. I hate when
| you execute something and a naked IO error ala ,,Permission
| Error" is returned. But this all means a lot of extra code that
| starts to hide the simple implementation under the hood. For
| simple apps where any error means ,,Sorry I'm out of luck" I use
| anyerror to not write custom FromError implementations. Again
| this all illustrates a bit that I'm not super happy in rust. But
| I actually never encountered an error system which can keep it
| simple and fast. The Zig examples are nearly there though! Just
| the missing error context ...
| masklinn wrote:
| > Unlike Rust's unwrap which will panic on error, I've opted to
| convert Error into an generic error
|
| Weird dig.
|
| Panic-ing is the entire purpose of Result::unwrap, if you want to
| convert between types you can... just do that.
| loeg wrote:
| I did not read it as a dig. Just a (very!) notable difference.
| valenterry wrote:
| I find this to be a good example where a language with well
| defined "foundations" can really shine.
|
| If a programming language supports union types and type inference
| then there is no need for such a "special" language feature as
| described here. The return type will simply have the form
| "goodresult | error" and the compiler will infer it based on the
| function-body alone.
|
| At the same time, the following problem of zig is automatically a
| non-issue:
|
| > A serious challenge with Zig's simple approach to errors that
| our errors are nothing more than enum values.
|
| If the result is "goodresult | error" then I can choose of what
| type "error" is and I'm not bound to any predefined types that
| the language forces upon me.
|
| In fact, the article then shows how to work around the issue with
| a tagged union. The problem is that this creates another issue:
| the composition of different error types.
|
| E.g. having function foo calling bar and baz and bar can fail
| with barError and baz can fail with bazError. Then, without
| having to do anything, the compiler should infer that foo's
| return type should be "goodresult | barError | bazError". This
| won't work if tagged unions are used like in the example - they
| will have to be mapped by hand all the time, creating a lot of
| noise.
|
| While Zig's errorhandling is not bad and miles above Go's, it
| makes me sad to see that we still lack ergonomy of errorhandling
| in so many languages despite knowing pretty much all of the
| language-features that are necessary to make it much easier.
| aldanor wrote:
| "goodResult | barError | bazError" method just don't scale;
| eventually, especially if using third-party libraries, you may
| end up with dozens of not hundreds of error types this way.
|
| The way Rust does it with its try operator (?), you have to
| tell it if it's barError (in which case it's up to you to tell
| it how bazError is convertible to it), or perhaps bazError, or
| something else altogether (e.g. sum type like you suggested,
| but conversion still needs to be supplied). There's also some
| libraries that provide error types for the lazy that are
| convertible from everything, so you can use try operator
| without much thinking, but for libraries you almost never use
| them.
| valenterry wrote:
| I use exactly that way of error handling and I find it scales
| very very well and makes refactoring a breeze.
|
| Nothing stops you from defining certain "borders" in your
| application where you wrap errors into something meaningful.
| E.g. if I write a file-IO library, I will return a custom
| "CannotOpenFile" error in my public methods which will
| encapsulate the underlying error(s) instead of returning a
| list of potential low-level errors had caused it.
|
| The difference is that with uniontypes it is _me_ who decides
| the level of abstraction and granularity, whereas without it,
| I 'm forced into manually handling errors types pretty much
| _everywhere_.
|
| Rust is an example where you are always forced to either
| align error types or where you lose information about what
| kind of errors it could be by using a more abstract error
| type. (which is the last thing you mentioned)
| jmull wrote:
| > there is no need for such a "special" language feature as
| described here
|
| I think the special language feature _is the point_ , not an
| unfortunate side effect of something lacking in the language
| design.
|
| The purpose of errors is to pass an indication of control-flow
| from the called function to the caller. That's why the language
| has "catch", "try", "errdefer", etc. This is about the language
| providing good flow control tools for the error path.
| valenterry wrote:
| Sure. My point is: if a language has certain other
| fundamental language features, then there is no or less need
| for those "specific" error features like errdefer.
| conaclos wrote:
| You are right. This result of the modeling of tagged unions in
| terms of enums. Hare [0] avoids this issue by using global
| tags: every type has a unique global tag. This allows merging
| tagged unions.
|
| [o] https://harelang.org
| MrJohz wrote:
| Wouldn't that mean that every type always has to include the
| type tag as an extra byte, making every type larger overall?
| Or is it only included if a union type is created from that
| type?
| conaclos wrote:
| No, the tag is only part of the union. The tag is
| deterministically and globally computed by the compiler.
| Tags are encoded in a fixed number of bytes.
| tsimionescu wrote:
| Sum types are still a poor error handling strategy compared to
| exceptions, which actually implement the most common error
| handling pattern automatically for you (add context and bubble
| up). Having the language treat errors as regular values fails
| to separate these fundamentally different paths through the
| code. It also makes the programmer re-implement the same code
| pattern over and over again.
|
| This cost may be necessary in manual memory management
| languages, where the vast majority of functions have to do some
| cleanup. There, having multiple exit points from a function
| through exceptions (or through early return statements) makes
| it harder to make sure all resources are properly cleaned up
| (especially when the cleanup of one resource can itself throw
| an error).
|
| But in managed memory languages, there's just no reason to
| manually re-implement this pattern, either through Go style
| regular values or through sum types. And note that the Result
| monad is not a good substitute for exceptions, as it doesn't
| actually add the necessary context of what the code was doing
| when it hit an error case.
| kosherhurricane wrote:
| > If a programming language supports union types and type
| inference then there is no need for such a "special" language
| feature
|
| Laughs in Go.
|
| > While Zig's errorhandling is not bad and miles above Go's
|
| Laughs again in Go. Go has no "foundations" regarding errors,
| it's just a convention. It has no union types. It doesn't have
| weird corner cases. It's just a returned value you can handle.
| Or not.
|
| Of all the error handling paradigms I've seen, Go's requires
| the least amount of "specialized thinking" (try/catch or
| whatnot)--it's just becomes another variable.
| grumpyprole wrote:
| Go's lack of sum types mean that there is no static check for
| whether the error has actually been handled or not. Go's
| designers went to all the trouble of having a static type
| system, but then failed to properly reap the benefits. Sum
| types are the mathematical dual of product types. It makes
| sense for any modern language to include them.
| Tozen wrote:
| A good point. Newer languages influenced by Go, like
| Vlang[1], have sum types partially for such reasons.
|
| [1]:
| https://github.com/vlang/v/blob/master/doc/docs.md#sum-
| types
| kosherhurricane wrote:
| > Go's lack of sum types mean that there is no static check
| for whether the error has actually been handled or not.
|
| I dunno, my IntelliJ calls out unhandled errors. I imagine
| go-vet does as well.
| grumpyprole wrote:
| A simple syntactic check will only ever work as a
| heuristic. Heuristics don't work for all cases and can be
| noisy. The point is, no modern language should need such
| hacks. This problem was _completely solved_ in the 70s
| with sum types.
| dthul wrote:
| Ever since I learned of sum types, they have ruined my
| enjoyment of programming languages which don't have them. I
| sorely miss them in C++ for example (and std::variant is
| not a worthy alternative). I don't understand why any new
| language wouldn't have them.
| koolba wrote:
| Pedantic typechecking is like learning to spot improper
| kerning, you think it's a good thing but you spend your
| entire life cringing at the world around you.
| valenterry wrote:
| Just wait until you learn union types, type classes, type
| providers, and so on. It's even worse afterwards. :-)
| lpapez wrote:
| In theory the lack of sum types sounds like a drawback for
| Go error handling, in practice it does not matter at all
| IMO.
|
| So far I have never worked a Go project without a strict
| linter enabled on the pipeline checking that you handled
| the case when err != nil. I don't care if it is the
| compiler or the linter doing it, the end result in practice
| is that there actually is no chance of forgetting to check
| the error, and works just as well as a stronger type system
| while also making the code stupidly obvious to read.
| grumpyprole wrote:
| > no chance of forgetting to check the error, and works
| just as well as a stronger type system
|
| A linter-based syntactic check is no substitute for a
| proper type system. A type system gives a machine checked
| proof. A heuristic catches some but not all failures to
| handle errors, it will also give false positives.
| martinhath wrote:
| How would errdefer work in the general union setting?
|
| Having errors as a first class construct in the language allows
| things like errdefer to be very simple and easy to use. It
| looks needlessly specialised at first, but I think it's
| actually a really good design.
| masklinn wrote:
| > How would errdefer work in the general union setting?
|
| Well it could not be, and some would argue that would be
| better.
|
| But you could also have a blessed type, or a more general
| concept of "abortion" which any type could be linked to.
|
| Or you could have a different statement for failure returns
| that way you can distinguish a success and a failure without
| necessarily imposing a specific representation of those
| failures.
| valenterry wrote:
| That's a very good question! Most advanced languages have
| some way of defining the concept of a "computation within a
| context". For example, all languages that support a notion of
| Monads do have that kind of support. Examples would be
| Haskell, Scala, F#, ...
|
| In those languages there are (or would be) generally two ways
| of achieving the same thing as errdefer:
|
| 1.) having a common interface/tag for errors
|
| In that case, if you have a return type "success | error1 |
| error2" then error1 and error2 must implement a common global
| interface ("error") so that you can "chain" computations that
| have a return type of the shape of "success | error".
| "success | error1 | error2" would follow that shape because
| the type "error1 | error2" is then a subtype of "error".
|
| 2.) Having some kind of result type.
|
| This would be similar to how it works in rust or in the
| example in the article here. So you would have a sumtype like
| "Result = either success A or failure B" and the errors that
| are stored in the result-failure (B) would then be
| uniontypes.
|
| The chaining would then just be a function implemented on the
| result-type. This is personally what I use in Scala for my
| error handling.
|
| Just to make it clear, this "chaining" is not specific for
| error-handling but a very general concept.
| throwawaymaths wrote:
| You can stuff your error payload in an in-out parameter in an
| options tuple. This way you don't lose errdefer and if you
| don't want it the code for it doesn't exist (deleted at
| comptime)
| duped wrote:
| > The return type will simply have the form "goodresult |
| error" and the compiler will infer it based on the function-
| body alone.
|
| I'm 0% convinced that inferred return types are a feature that
| a production language should have, ever. I've used it before
| and it's infuriating on top of being bad code.
|
| If a function is annotated f: T -> U where U is not an error,
| the contract with the caller is that f does not error, and
| cannot be changed to produce an error, and the compiler upholds
| this contract for all callers and definitions or redefinitions
| of f. It also means that another compilation unit can refer to
| f without having to type check its body, which sure, is a leaky
| abstraction, but empowers parallel compilation.
|
| Changing the signature is a breaking change and as such
| _should_ have a higher amount of friction. Changing the error
| type doubly so.
|
| And on top of all that, errors should not be non discriminated
| union types. The example is Result<int, int> - you need the
| error to have a discriminant to distinguish between a success
| and an error code.
| naasking wrote:
| > If a function is annotated f: T -> U where U is not an
| error, the contract with the caller is that f does not error,
| and cannot be changed to produce an error, and the compiler
| upholds this contract for all callers and definitions or
| redefinitions of f
|
| The OP is not suggesting that. They're suggesting that U can
| be an extensible union type that can be inferred from the
| code. Of course, if you declare it to not be such a union,
| then you guarantee no errors.
|
| Just think of it like checked exceptions from Java, except
| the exception signature is inferred from the function's code.
| If you explicitly declare "does not throw", then the compiler
| produces a type error that you have not handled all
| exceptions.
| golergka wrote:
| > Changing the signature is a breaking change and as such
| should have a higher amount of friction.
|
| Why?
|
| I'm all for very rigorous static type checking. But if we can
| make refactorings and changes frictionless -- let's. Having
| the ability to move things around and reorder them to work
| better results in more iterations and better code.
|
| As long as each iteration is very rigorously type-checked, of
| course.
| adamwk wrote:
| The issue is that the API can change unintentionally,
| breaking clients because of an implementation change.
| naasking wrote:
| Implementation changes can already break clients, except
| without checked errors/exceptions, they do so silently.
| adamwk wrote:
| Yes, certain language designs make it easier to break
| APIs for clients, just like the one mentioned above. I
| was pointing out that this is bad and we should try to
| prevent that from happening
| naasking wrote:
| No, the one mentioned above makes it easier to not break
| clients. A language that exports an API without checked
| exceptions can change the implementation and break
| clients silently in production. A language with checked
| exceptions will not break clients silently, ever. That's
| strictly superior from a robustness perspective.
|
| If, as a library author, you want to define a strict
| contract with clients that you never break, then you
| explicitly specify the exception signature and you don't
| let the exceptions be inferred. Then if the
| implementation changes in a way that introduces a new
| exception that doesn't match the client contract, then
| the API author gets the error at compile-time.
| golergka wrote:
| It seems that you're talking about a code that is exposed
| to some third-party clients where you need to maintain
| compatibility. This is, of course, a valid concern, and
| you're absolutely right that this layer, exposed to third
| party clients absolutely does need increased level of
| friction.
|
| However, most applications don't have any such exposure,
| all of their code is consumed internally. And even when
| they do, their external API surface which is actually
| exposed as some REST or ABI is minuscule in comparison to
| all the internal APIs -- method calls and types that are
| never exposed anywhere.
|
| I think that this problem, which is certainly a valid
| one, should be solved with methods which would not
| negatively affect developer experience of working with
| 99% of all the completely private APIs that don't need to
| be backwards compatible.
| valenterry wrote:
| As others have been said, I think there is a misunderstanding
| here.
|
| First, methods can (and often should) be annotated with their
| return types. And if a method is annotated as "T -> U" then
| it cannot return an error and trying to do so would fail the
| compilation - so I totally agree with you on that.
|
| But inside of (non public) application or library code, there
| are a lot of small methods calling each other before there is
| some higher-up public method that will be called not by me
| but someone else.
|
| And for those kind of methods type inference is great when it
| comes to refactoring. Even if I make a mistake and make such
| a method return an error even though it shouldn't, this
| mistake will be caught a bit higher up where the return type
| is annotated. In my experience this has almost no drawbacks
| and drastically simplifies refactorings.
| duped wrote:
| I don't think there's a misunderstanding, I stand by what I
| said.
|
| Inferred return types don't make refactoring easier. In my
| experience they make it much more difficult, because you
| have to look at function implementation to understand what
| it does. Particularly for error handling, you always want
| to see what errors a function may return _even in library
| code_.
|
| And all that said, there's no distinction to me between
| internals and externally facing code when it comes to
| quality and standards. If you have a function, even if it
| is called once, it should always have its return type
| annotated. It is more correct than leaving it to be
| inferred by the compiler, because you are explicitly
| annotating the intended behavior of the abstraction. This
| makes it easier to write correct code and for verifying it
| by a reviewer.
| nyberg wrote:
| The issue isn't as simple as just having better error unions
| with payloads. Constructing the error diagnostics object needs
| to be taken into account and the costs it brings (e.g does it
| allocate? take up a lot of stack space? waste resources when
| one doesn't need the extra information?). Such is a design
| choice for the programmer not the language as only they know
| which approach will be more effective given they have a clear
| view of the surrounding context.
|
| An alternative is to allow an additional parameter which
| optionally points to a diagnostics object to be updated upon
| error. Returning an error here signals that the object contains
| additional information while not harming composition as much
| and giving the choice of where to store such information. This
| is arguably a better pattern in a language where resource
| allocation is explicit.
| valenterry wrote:
| I think what you are saying is pretty orthogonal to what I
| was saying no?
| rootlocus wrote:
| I'm not really experienced with programming language design
| or with compilers, but it seems to me the design of a
| systems programming language has to compromise on the side
| of performance. If the implementation of the design
| requires additional space or cpu time, it may not be a good
| fit for the language. As such, it's not orthogonal.
| amluto wrote:
| The union approach fails when, say, barError is the type of a
| success return somewhere. Sum types (as in Rust, for example)
| avoid this problem.
| valenterry wrote:
| Well, yes and no. What you say also applies if two errors
| have the same type but you want to differentiate where it
| comes from. In all these cases you can wrap the result or
| error into something, which essentially means tagging it (but
| without actually needing sumtypes as a language concept.
|
| Sumtypes sure are useful, but I believe that except for
| GADTs, you can emulate sumtypes with uniontypes, but not vice
| versa.
| matklad wrote:
| Note that Zig supports general sum types and pattern matching.
| It however deliberately chooses not to use those features for
| idiomatic error handling.
| laserbeam wrote:
| It's actually rare that I end up needing extra stuff attached to
| errors in zig. When I do, I end up thinking of a higher unit of
| work where I can stick some context data in case of errors.
|
| For example, the article mentions a JSON parser. In zig I'd end
| up writing a class with minimal state and a few methods. I'd call
| parser.parse(...), and if that goes bad I just fill a
| parser.errdata field before returning an error. This is similar
| to the old errno.h way of doing it in C, but it's not global.
|
| That's the pattern I use, if I need detailed error information, I
| make sure to provide a pointer where they can get written. And it
| turns out I haven't needed that too often...
| throwawaymaths wrote:
| You should consider using options error payloads. Saw an
| article on zig.news
|
| Benefit is if you don't want the payload, the code to generate
| the payload gets compiled out.
| skybrian wrote:
| This seems to be it: https://zig.news/ityonemo/sneaky-error-
| payloads-1aka
|
| It looks like it would be useful, but relies on a naming
| convention? Hopefully everyone picks the same field names.
| throwawaymaths wrote:
| Or, read the docs on the function you're calling?
| skybrian wrote:
| I was thinking more about what happens if you're
| combining already-written code that uses different naming
| conventions, or even different types. Suppose you get an
| error in one format and want to pass it on in a different
| format?
|
| Writing a converter probably isn't so hard, but it's
| tedious, like having multiple kinds of strings.
|
| But it's probably too soon to worry about that when Zig
| doesn't have a package manager yet.
| dns_snek wrote:
| > But it's probably too soon to worry about that when Zig
| doesn't have a package manager yet.
|
| Just fyi in case you haven't seen the news, It launched
| with 0.11 release a couple of days ago.
| throwawaymaths wrote:
| Agreed. Probably tedious. But straightforward, unlike
| say, multiple inheritance, and, likely to be rare.
| throwawaymaths wrote:
| The article was updated: https://zig.news/ityonemo/error-
| payloads-updated-367m
___________________________________________________________________
(page generated 2023-08-06 23:01 UTC)