They stuck to their guns, took what C did, and made it 1000x better. And in the same sweep made a language that is dead simple to read and code review.
They stuck to their guns, took what C did, and made it 1000x better. And in the same sweep made a language that is dead simple to read and code review.
And Go isn't even close to 1000x better than C. It makes almost all of the same mistakes that C did (especially the billion dollar mistake), despite being new enough that it should have learned from them.
Go, like C, is a very get-it-done language. It doesn't let you have any fun at all with abstractions, so you end up just doing your work instead. In spite of its warts, I think Go is remarkable and unique for this quality.
People read code like this and think: its verbose and repetitive.
if err != nil { return nil, err }
But, I almost never write code like that. What I'm usually writing is some formulation of: if err != nil { return nil, fmt.Errorf("Error fetching thing: %v" err) }
Sometimes; you wrap to add additional context. Sometimes; you wrap to get a generic error type into a package-specific error type. Sometimes; you wrap to get, idk, some kind of project-wide HTTP-oriented error type. Error wrapping is the pattern in Go; which is very different from exception-oriented languages.I like this article's syntax for a straightforward "throw error" situation. I wouldn't support its addition, but I wouldn't oppose it either. However, I struggle to imagine a more concise Go-ish syntax I like which supports a "wrap and throw" type situation. Maybe something like:
v1 := Thing1()!
v2 := Thing2() ! fmt.Errorf("Error fetching thing: %v" err)
Phrased in english; the bang operator can follow any statement that resolves to a multi-value function return where the last value is an error type, and the statement is in a function body whose last return value is an error type. If this function returns a non-nil value as its last value; If nothing follows the bang, it bubbles up this non-nill error with no wrapping. If a statement follows the bang, that statement gains an implicit `err` value containing the error value the LHS statement resolved to; and it can return a new error-type which then gets bubbled up.But, again; I don't love or even really like this, its just the best I can come up with. Its not that much shorter than writing it out. Its not obvious how it should behave in the presence of an outer-scoped variable named `err`. Its not obvious how the bubble up should handle the other non-error return values (zero value I guess?)
One thing I rather like about Go is; if you're catching and re-throwing wrapped errors, which is a pattern I like, its actually far more concise than exception oriented languages. The same thing in JS?
let v;
try {
v = Thing()
} catch (err) {
throw new Error(`I died: ${err}`);
}
So; you rarely do that. But in Go, its barely harder to wrap and re-throw than it is to just directly throw; so people do it more often.The downside is that since you don't "need" to catch your exceptions, you don't think about them. That's one thing I really like with Go, errors are in my face all the time, so I have to think about them. Even if I write the infamous
if err != nil {
return err
}
at least I do it consciously. And when I or my colleagues read it 2 months later, we know from reading that this can return an error, and we see clearly how it was handled, so we can think about it and visually see if it was handled correctly.Reviewing and reading code with exceptions is a nightmare, because you have no idea if errors were handled correctly. On every call you have to guess if it throws or not.
Go made something super simple. I write and review a lot of Go code daily, and I don't quite get how these error branches are such a big issue. The code is always very simple to follow through.
So yeah, an alternative for those that be writing C otherwise, and at least Go helps making the world safer even if with a draconian language design.