5 ms·
What bothers me a lot more is that Go has somehow managed to get a trivial but crucial thing totally wrong: Some errors can only be identified by value and othe
by fauigerzigerk 10y ago
What bothers me a lot more is that Go has somehow managed to get a trivial but crucial thing totally wrong: Some errors can only be identified by value and others can only be identified by type.
There is no general and reliable way to handle specific types of errors.
And there is no way to add instance specific information to errors in a later version of a package, because that would require using an error type instead of (or in addition to) the existing well known error value, which breaks callers.
- themartorana 10y agoThat's not true. All errors adhere to the error interface [0] and can be parameterized as such. It's not significant different to subclassing a stdlib Error class in another language and using reflection or down-casting to get its sub-type. So almost all packages either return a base error, or a custom error type that adheres to the error interface. [0] https://golang.org/pkg/builtin/#error https://golang.org/pkg/builtin/#error
- nemothekid 10y agoI think you missed his point. >Some errors can only be identified by value and others can only be identified by type Let's say I'm reading a csv file, and I need to check whether I'm done reading the file - I'd do something like so: if _, err := csvReader.Read(); err == io.ErrEOF { quit() } However (I can't think of an example off the top of my head), there are many library that make you check the error type to find out what went wrong. if c_err, ok := err.(LibError); ok { c_err.Code } I commonly see this version in libraries that need to return an error with a non-static message (such as the `hostname goolge.com could not be reached`).
- rakoo 10y ago> And there is no way to add instance specific information to errors in a later version of a package, because that would require using an error type instead of (or in addition to) the existing well known error value, which breaks callers. Yes you can. Errors are interfaces, not values. As long as you define an Errror() method your struct behaves as an error, but it can have anything a struct has, such as specific information. You should read Dave's previous presentation (http://dave.cheney.net/paste/gocon-spring-2016.pdf http://dave.cheney.net/paste/gocon-spring-2016.pdf) to understand a bit more about what's already possible today and why Dave's package makes it easier.
- fauigerzigerk 10y agoInterfaces don't help in this case. Say you have a package called net: var Error = ... func Get(url string) (result, error) { if ... { return Error } } Users of your package would do something like this r, err := net.Get("http://example.com") if err == net.Error { //handle this particular error } What if you decide that there are in fact different types errors or you want to return an error code with each error? How would you do that?
- rakoo 10y agoYou should read Dave's presentation again. Asserting for a type is the wrong way to do it, your library should instead provide a helper for the specific parsing you want to do. So if you want to know if this error is temporary, you should have this, inside the net package: func IsTemporary(err error) bool { // specific parsing } Similarly, if you really want an error code: func ErrorCode(err error) int { // a type assertion, which is fine because we're inside the net package v, ok := err.(ErrorWithStatusCode) if ok { return v.ErrorCode } } (Note that in the standard net/http package, the result of an http call is inside the result, never in the error that is returned, whatever the status of the http call is) Basically your package should give all tools so that callers can take a decision without knowing about internals of the package. Knowing the exact error code is probably not what a caller wants; what they probably want to know is whether the error is temporary or fatal, if they can retry with a chance of success, if they can take counter-measures and if they can't alert the user.
- fauigerzigerk 10y agoI think I have read everything Dave Cheney has ever written about errors. But you misunderstand what I'm saying. The standard library is already littered with error values defined on the package level. Many third party packages emulate that approach. Once you have done that in package, there is no way to change it. You cannot add function call specific data to an error after the fact. There is no way to implement an IsTemporary(error) function once you have clients that compare any returned error to a package level singleton using ==. >Knowing the exact error code is probably not what a caller wants I had that need many times. For instance, I needed to get the SQLSTATE from database errors. There is a huge number of SQLSTATE codes and interpreting/classifying them can't be the job of a data access package or even a DBMS specific driver library. Also, how would a networking package know what I consider temporary? Some protocols like HTTP specify some errors to be temporary, but that may not coincide with what I'm willing to retry or not retry.
- tptacek 10y agoThis is true, but it's just as true of all the Ruby code out there that raises strings instead of error types, and all the exception-handling code that catches everything and then halfheartedly tries to disambiguate. The language problem is that laziness is enabled. The real, practical problem is that programmers are lazy. It would be nice if at some point the standard library would be scrubbed of string errors. But for the most part, the standard libraries errors that are sane to disambiguate are disambiguated, either with error types or helper functions.
- fauigerzigerk 10y agoAs I see it, it's mostly a standard library issue. Package level singletons make API versioning extremely difficult, but it's the most widely used approach in the standard library. And there are simply too many widespread ways to check error types: (a) err == pkg.Err (b) pkg.IsXYZError(err) (c) switch err := err.(type) If (c) is discouraged, as some have claimed on this thread, then how are we supposed to access type specific data?
- tptacek 10y agoI don't think (c) "is" discouraged, so much as Dave Cheney doesn't like it. I think (a) and (b) are the best ways to do this, though. Also: cards on the table: if there's no straightforward way to respond distinctively to different errors, I don't think error strings are really all that bad. I think it's reasonable to look at untyped errors as "this library function returns only one kind of error: it didn't work". A lot of legitimate interfaces are like that. Why spend extra effort typing their returns?
- fauigerzigerk 10y agoAbsolutely. If there is no reason to distinguish between different types of errors then defining different types is a pointless distraction that clutters the API. So let's simply return errors.New("...") or a private package level error value. But public package level singletons are a completely unnecessary burden on API versioning with no upside at all. Why on earth would I ever want to make a lasting commitment to never attach any data or call specific message to errors in the future?
- infogulch 10y agoSee Dave's previous post on this: "Don’t just check errors, handle them gracefully" [0] Here he explores the various possible implementations and their problems then introduces a good solution. In short: 1. Assert error behavior not type or message, i.e. the error has a specific method. NOT `type HTTPError struct {Message string;Code int;}` but instead `type HTTPError interface {error;HTTPCode() int;}`. This decouples the location and specific implementation of the error type from the extra information you're adding to it. (Corollary: Interfaces are Go's only real way to create custom generics. You'll be much happier if you can map your problem space onto interfaces.) 2. Number 1 doesn't help when you wrap errors, because the wrapping error hides any methods on the original. Dave's errors package helps with errors.Cause() that exposes the original error so you can apply #1 effectively. [0]: http://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully http://dave.cheney.net/2016/04/27/dont-just-check-errors-han...
- fauigerzigerk 10y agoAgain, I have read Dave Cheney's posts, and I totally agree with his conclusion that sentinel errors (i.e. public package level singletons) should not be used. I don't necessarily agree with his stance on error types, because sometimes we need access to specific data attached to errors (e.g SQLSTATE). But the standard library uses sentinel errors everywhere and third party packages often do the same. That's what creates the inconsistent and messy status quo of handling different types of errors. It's also not going to change because sentinel errors can never be changed into anything different without breaking clients of the API. No call specific message or other data will ever be added to these standard error values.
- infogulch 10y ago> sometimes we need access to specific data attached to errors (e.g SQLSTATE). Dave's post and my entire comment directly address this: Add a `SQLCode() int` method to the custom error, return `error` type and users can assert `codeErr, ok := err.(interface{SQLCode() int}); code := codeErr.SQLCode();` somewhere up the stack to get it. But yes, the standard library's use of sentinel errors is unfortunate and can't be fixed.