> That’s definitely not how errors work in Go, either in theory or practice.

Sorry, I originally had a footnote after that, which I mistakenly removed: something to the effect that "yes, I realise errors are actually nullable structs implementing an interface which can return a string". Not quite as detailed as your comment, and perhaps incorrect in some respects, but in short: yes I understand errors aren't literally strings in Go, but I wanted to focus on the essential aspect, not the distinction between 'is a string' and 'is an interface type whose only common denominator is the string it returns'.

(FWIW, I wrote Go professionally for several (painful, stagnant) years, so you can safely assume I have at least an intermediate familiarity with the language, and that any simplifications are just that.)

And yes, I'm aware you can do type assertions on errors. That's why I specified "any hacky matching solution [i.e. type assertions] is unhelped by the type system": in other words, you can type-assert on some random types if you know that's what the function can return, but the type system neither (a) tells you what errors the function can return (like Java does with checked exceptions, or Rust - among many others - does with the errors-as-values approach) nor (b) enforces that you have handled all possible error types.

In practice, this is a moot point because most Go code (that I've seen) doesn't really use the type system to structure its error handling anyway. If you look through any file at random in the Kubernetes source - probably the most vaunted example of 'well written Go code in a widely used application' – I can't find a single example of that being done, and very few examples of any error handling tout court besides "if err != nil { return [_,] err }".

> The nice thing about that is that you can then just do this[: "if errors.Is(..."]

That only works on errors created using the `errors` package. That also does runtime reflection on nullable pointers.

> The error you wrote is not what errors in the applications I work with look like.

Yeah, I'm used to the "it's ok if you ensure all the code you run is meticulously handwritten" attitude, from when I worked with Go. Unfortunately I have to use libraries, at which point that argument rather falls apart.

That's why I prefer my programming language to enforce or encourage good habits, rather than simply saying "well it's your fault for being rubbish!" if my, or a dependency's, code is problematic for me.

> “Originality” is what you want to see in research languages like Haskell.

I'm not criticising it for being unoriginal. I'm saying that your reverential comment that "the Go language designers have taken some fairly sophisticated lessons from history" is rather over the top.

I couldn't care less whether it's original or not, but you claimed it was, and I responded to that because it seemed a very odd claim (not because it's terribly important if true).

---

Look, I appreciate the arguments for Go. It's a simple language that avoids all that clever, computer-sciencey, type-theory-y argle-bargle, for simple salt-of-the-earth craftsmen who just want to write Good Honest Application Code in a language where no one cares about that garbage collection stuff because we don't write garbage (and anyway what's a few milliseconds here or there!? what's determinism? http go brr!). I tried to convince myself of that for several years. The truth is that there are plenty of simple languages which aren't designed with obstinate inadequacies of the kind amply illustrated by the OP (though I'd throw in the horrific 'zero value' semantics, along with the bug-inducing, heap-smashing 'nullable pointer as option type' monkey patch that's semi-standard among Go programmers).