fmt.Errorf("... %w", err)
>compare errors errors.Is(err, ErrorType)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.
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.
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.
I would recommend it for application code. For library code you should go the extra mile and use proper error types.
I think that Go's error handling is actually fine and most people are making a fuss about nothing, but even so Rust's error handling is clearly superior.
If you do need to know specifically what the error was then the API provides public error types.
So go doesn't do error handling well at all. Give your stdlib a stringly error maker, that's what will creep in everywhere. It doesn't matter if there is a section for creating (somewhat) typed errors - half of go code doesn't bother and together with the poor type system you can't even be sure which type of error you got.
Agree that Rust errors need to be better. anyhow and thiserror are fine libraries but I want that experience out of the box, ideally. There are ongoing efforts to improve things, so I’m hopeful.