Honestly if you aren't a skilled enough programmer to navigate the nuances of any particular language then you really are no better than kids playing in drag-and-drop environments like Scratch.
Honestly if you aren't a skilled enough programmer to navigate the nuances of any particular language then you really are no better than kids playing in drag-and-drop environments like Scratch.
result, err := Foo()
if err != nil {
...
}
over and over again in a language designed after 2000. The usual argument is that Go's simplistic design "reduces complexity", yet explaining something basic as why the error handling system doesn't behave the way one expects needs you to know how the compiler represents interface types.The next decade will see Go adding in most or all the complexity real-world software asks for, without ever admitting that maybe it should've been supported from the beginning without the hacky workarounds.
> Honestly if you aren't a skilled enough programmer to navigate the nuances of any particular language then you really are no better than kids[...]
Even though the bit in italics is pretty much the opposite of the "Go/JS pitch", I'll bite.
Sure, learning to code at a high level means you need to take time to learn things (which is the opposite of the pitch). I just don't get why teaching yourself to "navigate the [brokenness]" of a language that was out-of-date the day it was released is preferable to learning to write in an expressive language that won't artificially handicap you or provide you with an arsenal of footguns.
In all the time you were casting to and from interface{}, you could be exploring and using powerful and practical new ideas that make it less likely you'll suffer for failing to check one of those "err"s.
Honestly, I don't mind error handling in Go. I've used a wide variety of languages in production systems (I've lost count but it's more than a dozen) and I've found Go to be surprisingly good at giving detailed, context aware breakdowns of where issues arise and allowing me to easily handle them. Sure there are a thousand different ways to do this and Go picked the ugliest, but in spite of that I've found it to be surprisingly effective - even in the more complex projects I've written like murex (my alternative UNIX $SHELL) and the odd Linux FUSE file system I've written to scratch a particular itch.
In fact for as many complaints like yours I've read there are also as many seasoned developers complementing Go's error handling. So I really think that particular issue is more a matter of personal taste rather than poor language design.
However I'm _not_ going to defend the nil thing nor how interface{}'s are (ab)used as an alternative to generics. Those _are_ just bad design choices.
But for all of Go's sins, I still find myself more productive writing Go code than I have in any other language for a long time (probably since writing Pascal in Borland's Turbo Pascal back in the 90s). Hence why I defend Go. I can understand idealistic opinions about language design (Go is opinionated too after all) but frankly what really matters is a developers ability to get an idea into something executable. And I feel a lot of the complaints about Go really miss the point about how productive that language is to a great many people and without sacrificing too much control to be useful in a practical sense.
People who complain about it don't grok the Go ethos. That's fine, it's not for everyone.
How about you study the language a bit more in depth (including estabilished idioms) before pontificating about it?
I'll note that The Fine Article is about why one of those established idioms confuses newcomers. Even better, it bites people because the type of an interface needs to be nil for something to work. (Insert appropriate expression of astonishment here.)
Enforcing patterns hiding inadequacies in the implementation works less well than one would imagine, e.g. look at all the languages that Go sought to improve upon and the baggage of "design patterns", GoF-alikes, etc. that they brought with them.
> return early
I was just going off what I found at https://blog.golang.org/error-handling-and-go.
I assume you meant this (in Go-ish pseudocode)?
result, err := Foo1()
if not err {
Panic(err)
}
result2, err2: = Foo2(result1)
if not err2 {
Panic(err2)
}
This might allow you to skip the nesting of blocks, but why would you do this when multiple languages exist where you don't have to thread around error messages everywhere? For instance, result1 <- foo1
result2 <- foo2 result1
(Yes, that's Haskell syntax, but you can do things with a similar lack of pain/verbosity/error-prone-ness in many languages.)2. I think the arrow means assignment in Haskell and you are just referring to monadic errors? To use them the same way proper error handling is done in Go, you would just have more nesting and multiple unwraps, which is marginally different than Go syntax (but definitely with more compiler checking.) The arrow syntax in Go is used by channels.
a, err := squareRoot(x)
if err { handle(err) }
b, err := log(a)
if err { handle(err) }
c, err := log(b)
if err { handle(err) }
you can just use an optional ("Maybe") type: write a, b, and c with types like a :: Double -> Maybe Double
and then do a <- squareRoot x
b <- log a
c <- log b
If the computation of a fails, the whole computation fails. The compiler takes care of all the error-checking plumbing. I think the ergonomics of this common kind of situation are really suboptimal in Go, which to my knowledge doesn't support anything remotely similar.That's trivially feasible and still shorter than the Go version:
a <- decorate (squareRoot x)
b <- decorate (log a)
c <- decorate (log b)
Outside of the do context, your return value is just that, a value, you can manipulate it using the language's regular tooling. And you can decorate the do context itself if you want the same decoration for all calls in the block.At the end of the day it doesn't make any more sense to apply Haskell methodologies to Go than it does to complain that Haskell is missing some fundamental features of Go. They're distinctly different languages. But despite this I've noticed you spend a lot of time in various Go discussions on HN moaning that Go isn't more like Haskell.
https://rustbyexample.com/std/result.html
Ergonomic error handling or generics/parametric polymorphism aren't "Haskell methodologies". Go is one of a very small number of languages that have been designed in the last decade and lack features like this.
The reason I participate in HN comment threads about Go is largely how entertaining I find comments strenuously rationalizing Go's inadequacies. In one (very recent but definitely memorable) case[1], I was told that
> You [should] first reconsider your need to make a generic data structure and evaluate on a case by case basis.
(I'm not sure why you made that edit, but I've made a mental note of what the last bit said. Arguing on the internet is a difficult, if useless, skill, and I'd hate to be tiresome.)
func readNumberFromFileAndDoubleIt(filename string) (int, err) {
file, err = os.Open(filename)
if err != nil {
return 0, err
}
defer file.Close() // BUG!! this returns an error, but since we defer it, it is not going to be handled
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return 0, err
}
contents := string(bytes)
i, err := strconv.Atoi(contents)
if err != nil {
return 0, err
}
return 2*i, nil
}
Rust (may contain syntax errors): fn read_number_from_file_and_double_it(filename: &str) -> Result<i32> {
let mut file = File::open(filename)?; // file automatically closed at end of scope
let mut contents = String::new();
file.read_to_string(&mut contents)?;
contents.parse().map(|i| 2*i)
}
4 vs 15 lines, I think it's obvious which one is easier to readAny practical code using Results soon ends up wanting to mix the errors from multiple sources. This requires a lot of boilerplate effort to make everything interop, and the machinery to reduce this is both complex and not standardised. If you don't go the upfront boilerplate-and-machinery route, things look awful.
And of course, if you use something else, like an Option, you're back to
let foo = match bar() {
Some(foo) => foo,
None => return None,
};
Go is much more consistent, and less pathological.Your example is especially disingenuous, though. For example, you chastise Go with
defer file.Close() // BUG!! this returns an error, but since we defer it, it is not going to be handled
but ignore the fact that this "bug" is nonoptionally hardcoded[1] into the Rust program. Which is it then?Rust's error handling looks nice on fake examples, and manageable inside self-contained libraries. My experience of actually using multiple libraries is that Rusts error handling is a choice between tangly handling of nested errors or verbose attempts to early-exit.
[1]: https://github.com/rust-lang/rust/blob/master/src/libstd/sys...
As to specifically whether I have used error-chain, not really. I no doubt will in the future, but I'm not seeing Rust on my plate for a while.