The proposal has been partially accepted and implemented in Go 1.13: https://github.com/golang/go/issues/29934#issuecomment-48968...
Edit: spelling, genetics to generics.
Without generics you would have to either throw away the type information, reimplement a `Result` type for every value type, or copy/paste the implementation at each usage site (the common Go pattern).
Go's support for multiple returns itself is also a hack around the lack of support for tuples (which would be trivial to implement if you had generics).
I think the important part there - and the contrast with e.g. exceptions - is that an error is treated as normal, as one possible outcome of calling a method, instead of exceptional as exception-based languages seem to be. I mean yeah of course a file can not be available to open, that can be expected. (That is the simple use case of course, I'm sure there's much more complicated situations).
To a first approximation, the purpose of Go is to stop every codebase from reinventing its own tuples or Result<> type.
Go does have generics, the Go compiler could add a chaining syntax for trailing err interfaces (like rust ? / try!) without needing to allow user-defined generic types.
Rust has this too. You can throw away type information and incur a runtime cost just like with Rust by using "Trait Objects", which are Rusts version of interfaces, basically. Luckily most choose not to use this for error communication.
However I struggle to understand that throwing type information away and incurring runtime cost is the pinnacle of error communication.
Furthermore, the idea that this somehow prevents codebases from "reinventing" anything is absolute nonsense. Basically every Go lib out there has different error handling. Hell, I wrote a library of my own (similar to pkg/errors). The very items I listed in my OP showed how every lib has it. You have no idea how each Go lib reinvented the wheel to know how you handle their errors. All with runtime checking. Madness.
The ecosystem exerts a lot of pressure on developers to not reinvent fundamental things, in Haskell I practically never reach for a StrictTuple or StrictEither even when I'd like strictness specifically because they are clunkier and less well integrated with other libraries.
Scala is the only language that comes to mind with this issue (scalaz vs Cats) and my impression is that even they have mostly converged.
I would say Go has much stronger idioms than either of these languages, and wouldn't need to be afraid of people inventing their own fundamental types provided they're included in the standard library.
C/C++ is another example. There are a plethora of string types mostly tied to which base set of libraries you're using. If you're using the C++ stdlib, you have std::string. But if you're using Qt, you're using QByteArray and QString instead. And probably similar for GLib/GTK.
(Does std::string even have any encoding support yet? When I last used it, it was just a string of bytes without any encoding awareness.)
I used golang at an employer and you're right, it's a huge mess (and error handling is just one part of it).
However a few decisions are huge signals that they didn't really think through them. Error handling and the now-solved package management are two big ones.
I keep seeing those claims (e.g. "programming in the large"), but in my experience, practically all the decisions in the language make it more awkward and difficult, even unnecessarily so, to deal with large code bases.
golang's interfaces for instance, are optimized for a small use case, at the expense of them being useless for actually useful things, like tagging and fast code search. The way "methods" are assigned to structs is overly verbose, and makes it easy to have to jump around even several files to figure out all functions that are tied to a struct. The list is too large to list here.
Compared to my Rust code bases, I feel like you need a larger picture of the project to understand what smaller code pieces do.
Is that a good thing though? Not sure, honestly. Should I really feel I understand what a function does, without understanding the functions role on the project itself? How can I truly understand a function without understanding its role?
Go feels like it's easier to understand the implementation, but not the context/purpose. Arguably, this may not be good.
I still prefer Rust, fwiw.
Again, what specifically about golang that makes it that way, other than errors vs. exceptions (keeping in mind that any line in golang can panic)? And what's preventing you from writing similar code in another language? I used Scala before and we had Option and Result types, and treated exceptions as panics. The same applies to Rust.
I found that because of golang's verbosity, it actually made it harder to understand what the entire project was doing. e.g. code bases I worked with were littered with one off functions that amounted to what basically was a map or map+filter, where this would have been a one liner in any modern language, including Java, C#, Kotlin, etc.
Basically, I'm agreeing with the rest of your comment. I find that a "denser" language (to a certain extent) allows you to understand the overall project quicker. There's probably a sweet spot somewhere, and I feel that languages like Java and C# are quite close to it. Keeping in mind that you want a high level overview of a project when you start off anyway, there's no magic solution that will give you that.
It's just that I keep seeing the same claims repeated so many times, and it really seems that they're just being repeated without any basis at all, and repeated just because what the golang author's claimed. So far, almost everything I've seen has been hype.
Most of blame for the mess is that the error interface throws away meaningful information. Then the caller is left mentally checking how to handle the error. Do I value check? Type cast? String check? Does the library expose a function to check, or do I do it myself? Can I get stack traces in some cases without introducing a production cost?
I'm not sure if Go needs a different type of error interface (ie diff methods), or if it needs Generics to solve this properly in my view. But, I just know I've hated the experience.
After I switched to Rust it all felt so familiar, and yet so different. I believe Rust and Go share error patterns. Yet in Rust I'm never left wondering what an error is, what I should do with it, how I check for specific types. This is largely due to awesome enum support and of course generics. Does Go need this feature set to "fix" my complaints with errors? Probably not.. but I do think it needs something.
Though I will agree, I don't think the `if err != nil` is fundamentally the problem with Go's errors, at all. Sure, the UX could be improved, but that's another story all together imo.