I've found that Go has significantly more mental overhead when it comes to error handling than rust does.
In Go, I don't have type information to know what kind of error it is. In rust, I can typically just "match err", and let the compiler tell me what error types it could be, and then handle each of them.
In go, I always have to read the entire function that returns the error, and any functions it calls, and so on, and then I have to think about how to handle it.
Is it an error from the os package, like "os.Open"? Well, maybe I need:
if os.IsNotExist(err) { /* handle */ }
Is it an error from the net package? I might need:
var addrErr *net.AddrError
var invalidAddrErr net.InvalidAddrError
var otherErr net.Error
if errors.As(err, &addrErr) {
/* handle addrErr */
} else if errors.As(err, &invalidAddrErr) {
/* handle invalidAddrErr */
} else if errors.As(err, &otherErr) {
/* some other net error */
} else if errors.Is(err, net.ErrClosed) {
/* handle this network error which is _not_ a net.Error */
}
Is it an error that used one of the third party error libraries, like "github.com/hashicorp/errwrap"? I may need even more special handling.
The fact that "errors.Is" and "errors.As" requires me to know if an error is a "sentinel" one like 'net.ErrClosed', or a struct error, like 'os.PathError', and I have to know if the struct is implementing error on a pointer receiver or not is also really annoying since it means I have to constantly go check error definitions, even if I vaguely remember what errors a function returns.
And, what's worse, the type system doesn't help me! It won't tell me if I get any of these things wrong. I can use "errors.Is" and "errors.As" backwards, and the type system won't help. I have no way to determine what errors a function can return without reading hundreds of lines of code.
Frankly, Go's error handling has more overhead than almost any other language, and I find it surprising you find it simple.
If all I'm doing is bubbling errors up, then rust's "?" operator works fine, and is easier than Go's "if err != nil" boilerplate. If I'm handling errors, rust's features are helpful, and Go is so much mental overhead it usually makes me forget the problem I was trying to solve by the time I've drawn out the full diagram of possible errors something can return up the stack. Usually rust's type-system has enough info that I don't need to read any code to know what errors I need to handle. That's never the case in Go, because idiomatically errors are "type-erased" into the error interface.
Oh, and this is all not to mention that go facilitates making unhandle-able errors. If someone returns "errors.New" or "fmt.Errorf", you're stuck with string matching, while rust encourages making typed and handle-able errors. The number of times I've had my go code break because someone reworded an un-exported error string I had to match on is pretty high. Hasn't happened to me in rust yet.