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.