2 ms·
The problem in Go isn't that the semantics are wrong, it's that the error handling is so prevalent that it starts obscures your logic. I'm writing an app right
by atombender 8y ago
The problem in Go isn't that the semantics are wrong, it's that the error handling is so prevalent that it starts obscures your logic. I'm writing an app right now where almost every piece of high-level code is:
if err := doThis(); err != nil {
return err
}
if err := doThat(); err != nil {
return err
}
if err := doThisOtherThing(); err != nil {
return err
}
// ...
The patterns in Go code are typically either (1) "call this and bail if error" or (2) "call this and bail if error, wrapping the error in a new error", with a sprinkling of (3) "if the error is io.EOF or whatever, do something else".
The #1 case is so dominant that Go really cries out for a built-in error-propagation mechanism.
The other problem with Go's error handling is the use of multi-value return types. I suspect Go's designers thought this was elegant, but it confuses things, because all error-returning functions return either a value or an error. Never both. It's not two return values in actuality, it's a sum type (aka product type or union). I'd take this over the current scheme:
func GetFile() (io.Reader | error) { ... }
And then maybe:
switch t := GetFile().(type) {
case io.Reader: ...
case error: return err
}
which can have the following semantic sugar:
r := try GetFile()
Rust has a macro that does something like the above, but the type system supports sum types and matching already.
Now you can do:
r := try GetFile()
count := try r.Read(buf)
instead of:
r, err := GetFile()
if err != nil {
return err
}
count, err := r.Read(buf)
if err != nil {
return err
}
- dilap 8y agoagreed; there's real (potential) niceness in these ideas.