4 ms·
Except when you do (1), your "wrapped" error can longer be compared. You end up with the same Rust-style boiler plate of building your own error structs or you
by nemothekid 4y ago
Except when you do (1), your "wrapped" error can longer be compared. You end up with the same Rust-style boiler plate of building your own error structs or you eschew the ability to inspect your error types at all.
For example, if I have some library that makes a network request and that request times out do I:
1. Return net.Error?
2. fmt.Errorf?
3. My own custom type
The problem with (1), is that API is invisible; the caller is now expected to trawl through my code or documentation for any errors I might return that the caller might want to handle. (2) prevents the caller for acting on my error without string mangling (and again has to inspect my code for the exact strings to handle. (3) is the best, but has the same level of boilerplate; and is also, I've found, unidiomatic in Go.
The second blight of Go's error handling situation (after if err == nil), is errors are rarely part of an APIs contract; especially outside of the standard library. Often times if you want to handle a special error code for a given API it means actually opening up the source code and hunting down where that error is thrown and hoping that (1) the error type is public or (2) that the library author won't break your string mangling in a later version.
- c7DJTLrn 4y ago>Except when you do (1), your "wrapped" error can longer be compared But that's wrong. When you call String() on it, it will look like the error is just a concatenated string, but it preserves all information about the underlying error, that's the whole point of the %w directive. https://go.dev/play/p/ZUGCKJDs9N5 https://go.dev/play/p/ZUGCKJDs9N5
- nemothekid 4y agoNo; I don't mean the underlying error. I mean the wrapped error. Let's say I'm writing an API client that uses net/http underneath - func CreateUser(username string, password string) error { _, err := client.Get("https://myapi.com"); return fmt.Errorf("Failed to create: %w", err) } Then I create another method like so: func SSOLogin(provider interface{}, token string) error { user, pass, err = provider.login(); if err != nil { return fmt.Errorf("Failed to login: %w", err) } if err := CreateUser(user, pass); err != nil { return fmt.Errorf("Failed to create: %w", err) } } There is no way for the API consumer to discover if the error happened at provider.login() or CreateUser (except for calling String, and Finding "Failed to create". The wrapping is one layer deep, and to go any further you must create your own types. This commonly happens in larger codebases; and what happens is you must define your own error types if you expect your code to be consumed by others. To be clear, there are way to solve this (creating your own types), but I don't think go handles that better than Rust, and the tools that Go does give you push developers in wrapping/concatenating errors into rather opaque types (which is mostly fine for webservers, you can just throw an error 500 and expect the user to try again). But that, plus `if err == nil` makes error handling in Go rather sore IMO. It's easy for developers to write, but not for library users to use, or for later users to maintain.