EDIT
Inspired by this comment: https://news.ycombinator.com/item?id=40220147
I forgot about exception stack traces, including "chained" exceptions. These are incredibly powerful when writing enterprise software that commonly has a stack 50+ levels deep.
Error returns are no different, assuming a proper implementation like the Result type in Rust. The difference is, unhandled error returns are found at compile time but unhandled exceptions only show up at runtime, when it's too late.
> Another point this is overlooked in these discussions: Exceptions and error codes can, and do, peacefully co-exist.
Both Go and Rust have panics, which are basically exceptions that are generally not supposed to be caught. They are used for unrecoverable cases like running out of memory or programmer mistakes. There's otherwise no reason to mix the two.
> Another thing about exceptions, especially in enterprise programming, you can add a human readable error message. That is not possible when only returning error codes.
I don't really know what you mean, it's equally possible in both cases. If anything, the error return implementation that Go uses is probably the most optimal out there when it comes to error messages. Most look like:
return nil, fmt.Errorf("opening file %s as user %s: %w", file, user, err)
Whereas most exception code will just dump a stacktrace since that's the default.Because fundamentally the function you called can return different errors at any point so if you just propagate the error the code paths are in fact not spelled out at all because the function one above in the hierarchy has to deal with all the possible errors two calls down which are not transparent at all.
There's no requirement for the calling function to handle each possible type of error of the callee. It can, as long as the callee properly wrapped the error, but it's relatively rare for that to be required. Usually the exact error is not important, just that there was one, so it gets handled generically.