Imagine writing Java, and wrapping every function call in a try/catch block, and inspecting the exception if one was caught, and then handling it or re-throwing it. That's kind of what we do in Go. There are no "unexpected errors" because it is clear where all errors originate and how they are to be handled.
Go2 will improve on this if err != nil syntax, possibly with some function-scoped error handler and a new language level handle/check concept. Check it out
https://dev.to/deanveloper/go-2-draft-error-handling-3loo https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback
Forcing every intermediate layer in the call stack to catch and rethrow that exception (check and propagate an error code) sounds like a good thing for explicit error handling, but actually in practice introduces a lot of boilerplate code that just provides more opportunities for mistakes (like errors being silently swallowed, or logged multiple times creating confusing log files).
How many times in a code review do you see `if err != nil { return err }` and ask yourself if what it is doing is actually appropriate? Most people just mentally mask out that kind of boilerplate over time.
But it is very verbose error handling.
It's not that they are inherently bad so much as it requiring discipline to stop things from getting out of hand, and that's just something you want to avoid in larger projects. If you need to be disciplined you might as well just check the return error value.
And there's a new class of bugs: forgetting to check for an error. In Rust, the compiler won't let you forget. With exceptions, forgetting to handle an exception will result in a somewhat controlled termination process. In Go, execution will just continue in a bad state.