Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I can't help but cringe whenever I read Go code because the following:

        paths, err := filepath.Glob(dir + "/*.png")
        if err != nil {
            panic(err)
        }
is repeated over and over again.

Didn't we learn in the 90's that exceptions are far, far superior to return codes?



Short answer no.

Long answer, the Go authors (Rob Pike et all) have a pedantic issue with exceptions as implemented in Python, Java, etc. They feel they conflate errors and exceptions. Errors, unlike exceptions, are an expected part of the programming process. Exceptions, on the other hand should be for exceptional circumstances.

I did not say that as well as they do so see what Pike has to say about the issue here: http://groups.google.com/group/golang-nuts/browse_thread/thr...

here is an a typical example of the Go community's attitude:

""" I _especially_ don't want exceptions to become an oft-used alternative to multiple levels of error return, as at that point they deteriorate to action at a distance and make understanding large code bases much harder. Been there, done that, got the scars to prove it. (Mostly from C++ and not from Java, but I've seen enough Java to have a healthy fear of runtime exceptions.) """

       -- Giles Lean


Here's a good Rob Pike quote from later on in the thread:

"This is exactly the kind of thing the proposal tries to avoid. Panic and recover are not an exception mechanism as usually defined because the usual approach, which ties exceptions to a control structure, encourages fine-grained exception handling that makes code unreadable in practice. There really is a difference between an error and what we call a panic, and we want that difference to matter. Consider Java, in which opening a file can throw an exception. In my experience few things are less exceptional than failing to open a file, and requiring me to write inside-out code to handle such a quotidian operation feels like a Procrustean imposition.

Our proposal instead ties the handling to a function - a dying function - and thereby, deliberately, makes it harder to use. We want you think of panics as, well, panics! They are rare events that very few functions should ever need to think about. If you want to protect your code, one or two recover calls should do it for the whole program. If you're already worrying about discriminating different kinds of panics, you've lost sight of the ball."


Not really, no. I've always found them more trouble than they're worth. Explicit, in-line error handling works much better for me.

I'm not sure what you mean by "over and over again," in this context, though. There are only three error checks of that kind in this program. It's an unusual program anyway, in that _any_ error condition jumps to the same path: displaying the default image. In most programs you want finer control over error handling than what's shown here.


I doubt that's really the case in proportion to lines of code. Three error conditions handled by one and the same handler doesn't seem unusual at all. Even 10 to 1 or more wouldn't be unusual I believe.

In my view, all attempts at making a distinction between recoverable and non recoverable errors inevitably fail simply because it is a non local distinction that cannot be made for any particular piece of code in isolation.


Reading the same code I also cringed, but for a different reason: the validity of `paths` is tied to the validity of `err`; you should never look at at `paths` without first checking `err`. We can solve this much better using sum types (also known as discriminate unions):

    // Pseudo Go:
    maybe_paths := filepath.Glob(dir + "/*.png")
    case maybe_paths of {
      Some paths: {
        // Do things with paths
      }
      None: {
        // Handle error case
      }
    }
This makes it impossible to use `paths` if `Glob` returns an error, as `paths` won't be in scope!

Furthermore, not having sum types forces us to include a distinguish value `null` for reference (pointer) types, as a way to communicate "no value". This is bad as now we cannot distinguish references that should never be `null` from those than can be `null`.


You can distinguish between nulls which are errors and nulls that are just the actual return value. That is what err is for.

The entire reason Go has multiple return values for functions (such as paths and err in this case) is to avoid having to encode errors in the "real" function result.

Now I agree, that this is less nice as say pattern matching and real sum types, but if you know how to appreciate those just switch to a functional language!


> The entire reason Go has multiple return values for functions (such as paths and err in this case) is to avoid having to encode errors in the "real" function result.

That's nonsense, MRVs are tuples (conceptually, Go implemented them with special syntax because... well, it's Go so it could not go with the general principle now could it?), so Go very specifically encodes errors in the function result.

And using a sum type would encode the error in the type, not in the result itself.


Multiple return values are not tuples, Go doesn't have tuples in the type system.


> Multiple return values are not tuples

Multiple return values are a special case of tuples. Hence the "conceptually" part.

> Go doesn't have tuples in the type system.

Could you explain how you managed to completely miss the part where I say exactly that?


This kind of fussy type system is at odds with Go's design goals. It would make the language more abstracted from the underlying machine for little gain. I can't think of an instance of the issue you describe causing problems in real Go code.


> This kind of fussy type system is at odds with Go's design goals.

There is nothing fussy about it, and the only way in which it's at odds with Go's design goals is that Go's design goals include things like "fuck you, only core types get to be generic", "that these mistakes were resolved 20 years ago does not mean we're not going to re-introduce them" and "if we give it a different name it's not the same thing, nah nah nah".


The problem it introduces is that you could have gotten rid of nulls from the language if you use sum types for errors.


There are some real trade-offs with using exceptions, as per http://google-styleguide.googlecode.com/svn/trunk/cppguide.x... . However, as Go is garbage collected, some of these trade-offs don't apply.


The two decades of experience we've had since the 90s, building things with exceptions, have fairly adequately demonstrated that they're not superior to return codes, despite their (apparent) theoretical advantages.



A lot of those criticisms seem to be based largely on Java - which, with its checked exceptions, is pretty much the worst case in the use of exceptions in a language design.


A lot of people in this sub-thread don't buy that return codes are inferior, but they really are. The issue is that the low level code can detect the error and knows some strategies that could be done about it but it's only the high level code that knows what's best (here's the best possible approach [1]). If your library is used by a batch job running on some network-detached headless server it will probably have to handle errors differently than a fat client would.

[1] http://www.gigamonkeys.com/book/beyond-exception-handling-co...


> A lot of people in this sub-thread don't buy that return codes are inferior, but they really are.

What a great argument, I know better!


Do you know how to read? I explained why in the rest of the comment and even provided an article that expanded on it!




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: