3 ms·
With your package, how do you suggest users of your function handle the error upstream? Like this? switch { case strings.Contains(err.Error(), "close th
by randomdata 2y ago
With your package, how do you suggest users of your function handle the error upstream?
Like this?
switch {
case strings.Contains(err.Error(), "close thing foo"):
// deal with foo close error
case strings.Contains(err.Error(), "close thing bar"):
// deal with bar close error
}
And if so, what lead you or your organization to prefer "stringly-typed" errors over the idiomatic approach?
- TheDong 2y agoStringly typed errors are also idiomatic in go, if you follow the stdlib. Like, are you doing anything with TLS? String matching: https://github.com/golang/go/issues/35234 https://github.com/golang/go/issues/35234 Using the stdlib ssh stuff? String matching: https://github.com/golang/go/issues/45207 https://github.com/golang/go/issues/45207 / https://github.com/golang/go/issues/39259 https://github.com/golang/go/issues/39259 Want to parse an address + port? netip.ParseAddrPort only returns strings ('errors.New' errors). http/http2 is also a minefield of half-exported errors. The go authors say to use 'errors.Is' and 'errors.As', but the go stdlib also defines an idiom, and the idiom it defines is that somewhere around 30% of all errors should be stringly typed, including many where you may want to have specific handling for them.
- randomdata 2y ago> Stringly typed errors are also idiomatic in go, if you follow the stdlib. Realistically, you can't follow the standard library, except perhaps the newest additions. Idioms emerge and evolve with use. Much of the standard library was written before Go saw much use, being largely in place before the world got to see Go for the first time. Also, thanks to the Go1 guarantee, cannot be changed now. If the aforementioned package was written in the 2000s, then it might be fair to say that it was in line with the idioms of the time. But it appears to have been written within the last year, and thus is not aligned with idioms of its age. That's not to say it has to be. Idioms are not requirements. The "stringly-typed" design may be justified even knowing what we know in the 2020s. And, with that, we don't need to speculate about the justifications. We can let the author speak for himself as to why the choice was made.
- TheDong 2y agonetip is one of the newer additions to the stdlib (added in ~2022), and follows the venerable stringly typed error idiom. I always interpreted the preference for stringly typed errors as a way to keep the Go language simpler. Good error handling is complicated and hard to read, and one of Go's values is that programs should be easy to read. As such, if you want good error handling, you should use a different language, like Java or Haskell or C++. This also helps keep people who might demand complicated things like generics away from the language, further keeping it simple. My understanding was that many of the go idioms are there to scare off programming language theorists, who have a tendency to unnecessarily complicate everything with type theory, and error handling also seems to mostly be in that vein.
- randomdata 2y ago> and follows the venerable stringly typed error idiom. Not exactly. It assumes that all error conditions within the functions provided by netip are of the same nature as it pertains to a single unit of work. In other words, there is only one type (not referring to the language's type system). Error type reuse where different failure points produce the same type of error does not violate current idioms. I cannot immediately think of any reason for why their assumption is wrong, so unless you have other ideas? That is not the same situation as the deferred close wrapper, though. It is assuming that closing multiple file handles is the same operation, but clearly that's not true. If you were, say, writing a copy function the error handling of the read handle failure is unlikely to be the same as the handling of the write handle failure. The former doesn't tell you much, the latter is quite actionable. The failure points are distinct, and thus of different types (again, not referring to the type system). The author's answer was basically that he is the only caller so if that problem arises in his code he'll simply modify the function to return idiomatic types. Which is fair for the lone wolf developer. When working alone anything goes! But it is not good API design generally speaking. It certainly wouldn't fly in something like the standard library or anywhere you have other developers.
- TheDong 2y ago> Error type reuse where different failure points produce the same type of error does not violate current idioms. I cannot immediately think of any reason for why their assumption is wrong, so unless you have other ideas? I have a program that takes user input and parses it, and then displays an error. My program is for a language other than english so having it display a pop up with the message "invalid ip:port, square brackets can only be used with ipv6 addresses" in english is bad. Therefore I want to switch on the error message to display translated errors, but of course Go does not think that parsing errors are something that is important. If parsing user input isn't a place to expose clear non-stringly-typed-errors, I don't know what is. Note, it also gives bad errors in that some of them include details and the user input, and some don't, so displaying them to users will stutter or require parsing. For example: _, err = netip.ParseAddrPort("foo:bar") // invalid port "bar" parsing "foo:bar" _, err = netip.ParseAddrPort("1.2.3.4:") // no port So in one case it has included the original user string, to make it clear what failed, and in another case it doesn't, so I'll have to always add in the context of the input (i.e. `fmt.Errorf("error parsing %q: %w", input, err)`) anyway in order to know what failed, but it'll stutter in every case where they do include the input. I know the answer to all my issues is the usual go thing of "a little copying is better than depending on the go stdlib" of course. At least for netip, forking it is fine, having to maintain a fork of the go net/http stack just to get halfway decent errors is a real pain.
- jrockway 2y agoerrors.Is is true for whatever x.Close() returned. If I'm going to do something upstream based on the error, I use a type. Most of the time, it's just log.Error text though.
- randomdata 2y ago> errors.Is is true for whatever x.Close() returned. That may get you through if there is only one x.Close, but now you have to offer the guarantee to the callers that there will only ever be one Close error returned. Furthermore, any callers of your function have to ensure that they don't end up introducing additional Close error returns in the same vein. Without such guarantees, you, the caller, need to resort to string matching to protect against undocumented functionality and/or future modifications when you handle the error. Sounds like a rough situation... > If I'm going to do something upstream based on the error, I use a type. Implying that you are a lone wolf developer? I think you make a good point that if you exist in your own world without other developers just about anything goes. That said, the language used around the previously linked repository implies that it welcomes other developers using and working on the code, so it is not clear how you are "doing the upstream" in all cases.
- jrockway 2y ago> That may get you through if there is only one x.Close, but now you have to offer the guarantee to the callers that there will only ever be one Close error returned. No you don't. It's a multierror, a feature of the standard library. errors.Is( errors.Join(ErrA, ErrB, ErrC, errors.Join(ErrD, ErrE, ErrF)), ErrF) is true. This is a feature in the standard library. > Implying that you are a lone wolf developer? You know that's not true from the history of the repository. We do have coding standards that reviewers enforce, this is one of them.
- randomdata 2y ago> It's a multierror, a feature of the standard library. Understood (I read the code), but that doesn't help, and really has nothing to do with the topic at hand. Is the problem here that you don't have an understanding of what we're talking about? > You know that's not true from the history of the repository. Which is why the claim was identified as being strange and in need of clarification. How can "If I'm going to do something upstream based on the error, I use a type." be true? If there are many developers, you don't get to control the upstream.