Go’s error handling is still cumbersome and lacking. I love writing Go but I don’t want to ever adopt anything like. It’s bending over backwards to achieve something sum types provide and this pattern is a mess.
I thought so too, after years with Scala and Rust. Now I think (X, error) is fine, indeed I think it is great for it's simplicity. I might want to have a safe assignment
// x() (X,error)
x != x()
// x is X
// return on error
The problem is indeed composition. How do I chain 3 calls that short-circuit on the first error? In Go that's verbose in the extreme. With exceptions it's easy to miss an error. Sum type errors have neither problem.
If you want to chain 3 calls and short circuit on the first error, don't use Go. I like explicit code without magic that I can't see.
for a <- a()
b <- b() {
...
}
I don't know what is happening there. Is it summing up errors? Is if short circuiting? Does it have error handling? Is it async? Is it a monad stack with transformers? That code could mean anything. Good luck coming back to that code after six months. I think the Sum type solution focuses on the happy path, the Go solution assumes you need to focus on the things that go wrong.
Additionally you have that nasty dependency of the Result type of a() to b() to make it work. And I've spent hours creating the right Transformer stack to compose more than two monad types like Result, Future and IO.
is more verbose but less so compared to the "just return error" case.
If you want to get rid of that, you need to have some auto conversion magic. This the way Rust does it, but it adds magic, you can't see the error handling, Rust needs (slow compiling) macros for this etc.
Checked Exceptions are nothing but errors as values with some syntactic sugar for the most common use case (bubbling up the error).
Gos version of value errors is just micrometers ahead of C style error codes. In both cases you get told "there could be an error", the error is a value of one single type (error/int), and you have to manually find out which different errors this value could represent.
If you want to know what you're missing, check out Rusts error handling.
I’m not talking about the underlying model, I’m talking about the control flow. What I mean is errors are explicit values belonging to the signature of the functions.
Go has insanely good tooling and very fast single binary compiling.
While all these languages (afaik) can reach similar levels of functionality (GraalVM e.g.), it's more work. As much as I hate the language Go, I can't deny how braindead simple it is to just make a tool with it. I don't need to choose a build tool, or a runtime version, there's a library for everything and most developers with more than a room temp IQ can immediately start working on it.
The only other language that currently comes close is Rust. If only they had stuck to using a GC, I'd be in heaven.