3 ms·
I agree that there is room for improvement, but I don’t mind Go’s errors that much. Using a linter to make sure errors are checked doesn’t seem like a major pro
by throwaway894345 3y ago
I agree that there is room for improvement, but I don’t mind Go’s errors that much. Using a linter to make sure errors are checked doesn’t seem like a major problem (you have to run a linter anyway, so what’s the harm?); most Go developers reflexively check errors for everything besides fmt.Println anyway. It would be better to put this in the compiler I suppose, but not a major deal.
Also worth noting that Rust doesn’t require you to use errors either; unused errors are a warning in the compiler unless the return type is a result AND you’re trying to access the valid data. This is a better than Go, but not by much in practice.
The error interface doesn’t bother me too much either. Just use errors.Is/As to determine the type of you’re going to do something special with it. It’s way better than having to create unique error/result types for every function.
Who should add context is definitely a problem, but I’ve settled on “the callee”, but in cases where you’re calling something that doesn’t add context you will need to add it in the immediate caller.
Adding context in Go is significantly easier than in other languages (yes, you can use anyhow in Rust, but it’s not considered good practice to put this in library code), and good context largely obviates the need for stack traces anyway. Context is nicer because it can tell you, for example, which loop iteration you were in when things blew up or what the salient parameter values were—stuff you don’t get from a stack trace. Of course, you have to do a bit of work for this benefit, but fmt.Errorf makes this super easy.
Logging also irks me. You can pass a logger like any other data, but mostly people just use global loggers. I haven’t had the multiple loggers problem, but that’s because library authors in Go idiomatically do not add their own logging. What are the languages that do logging well? I’ve had a horrible time with Python (and I think Java but it’s been 10 years).
- simiones 3y agoUnfortunately, fmt.Errorf makes errors.Is/As useless. In fact, errors.Is is mostly useless in general, since very few Go libraries have any error types at all. You're usually stuck with parsing error messages if you actually want to handle errors programmatically, even for much of the standard library.
- philosopher1234 3y agoUh? If you use `%w` in fmt.Errorf(), it should still work with .Is and .As?
- pkd 3y ago> Uh? If you use `%w` in fmt.Errorf(), it should still work with .Is and .As? You still need a stable error to compare against in errors.Is. fmt.Errorf is not going to provide that if the message is dynamic - which is almost always.
- throwaway894345 3y agoI think what you're talking about with "stable" and "dynamic" are error constants (like `io.EOF`) and instances of error types (like `url.Error` https://pkg.go.dev/net/url#Error https://pkg.go.dev/net/url#Error). You use `Is` for the former and `As` for the latter; fmt.Errorf's %w directive doesn't inhibit either case.
- simiones 3y agoThe problem is that the first-level error in most Go functions is fmt.Errorf(some error message, some args). The idea of pre-allocating an exported global error object (like io.EOF) is very rarely useful, and error structs are very rarely used either, because they require much more ceremony than just returning a fmt.Errorf() (or even an errors.New()) on the spot. But errors.Is is only useful if you take the first option, and errors.As is only useful if you take the second option. So, in practice, neither errors.Is nor errors.As are terribly useful, and %w just gives you a false sense of usefulness.
- throwaway894345 3y agoThis isn’t my experience. Most popular libraries seem to give an error type or value at least when someone might reasonably want to match on it. But yeah, if your coworkers don’t do this then Go can’t stop them (and in any case, I don’t see how fmt.Errorf() is to blame).