8 ms·
Why Go Gets Exceptions Right
- Jabbles 14y agoAs much as I like Go, I don't think we need to have an article about it every day, especially one that is several months old and contains wildly out of date statements like "panics are always fatal". http://golang.org/ref/spec#Handling_panics http://golang.org/ref/spec#Handling_panics
- motter 14y agoThanks for pointing out that this differs from the specification. I found this quite thought-provoking about different error handling techniques, so thought it worth submitting.
- repsilat 14y agoTo be honest I don't think there's any practical difference between this and using `errno` in C. In fact, I suspect Go may have adopted a more complicated solution simply because we're squeamish about using global variables. At the end of the day this solution doesn't work for "high level" code for the same reason `errno` and return codes aren't adequate - is forces the user to litter their code with explicit checks for exceptional cases. The whole point of exceptions is that they let the user write the code as if everything is peachy, to let the code bail out wherever appropriate, and to pass the buck for fixing it to someone who cares. (I don't know anything about `panic`/`recover`.)
- chawco 14y agoThis was my assessment as well -- it's just a cleaner way to use errno. I do think a language got this system right -- conditions and restarts in Common Lisp solve this problem very well. I'm a bit perplexed that I haven't seen this model adopted a bit more widely in the programming world, particularly given how much of a headache exception semantics are.
- repsilat 14y agoHah, I have a bookmark on the CL approach I've been meaning to get to for months now. I guess I'll have to give it another whirl. Ack, and your sibling comment is pointing me to a monadic approach... I'm sure "learn about monads" is also languishing on my to-do list somewhere.
- motter 14y agoDo you have a link to the CL approach? Edit: A quick search turn up this interesting LtU discussion: http://lambda-the-ultimate.org/node/1544 http://lambda-the-ultimate.org/node/1544
- repsilat 14y agoI'm on a different computer now, but iirc the one I have bookmarked is http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html http://www.gigamonkeys.com/book/beyond-exception-handling-co... Thanks for your link too.
- gregschlom 14y agoVery interesting article, thanks for sharing. I just submitted it here: http://news.ycombinator.com/item?id=4521675 http://news.ycombinator.com/item?id=4521675
- chawco 14y agoThis entire book is a really good into to CL.
- saurik 14y agoThere was a great thread in a previous discussion on HN about error handling that had a reply from lkrubner (currently, and likely forever, the top response on that thread) that went into some detail, causing more ensuing discussion. (edit: Ah, and then he links to that same article you linked to!) http://news.ycombinator.com/item?id=4468849 http://news.ycombinator.com/item?id=4468849
- eru 14y agoDo you have any opinion on using Monads for exception handling (like in Haskell)?
- Lambdanaut 14y agoI like them. The only annoyance is that you're forced to wrap your return value in a monad even if the function succeeds. That's type safety for you!
- eru 14y agoYes. But that's because the default is non-monadic wrapped and an assumption that it can't go wrong. I'd had to see non-monadic wrapped values to silentry go wrong or raise an exception. (You can do something like Go's panic in pure values. But you are not supposed to do that for recoverable conditions.)
- Evbn 14y agoIn practice, wrapping a return value in a monad looks exactly like returning a regular value in another language: return x.
- dllthomas 14y agoIt's a way of getting context-specific (but not error-specific) behavior, and when dealing with functions producing a polymorphic monadic value it threads very nicely. That said, the approach in Haskell of fail always taking a string (which doesn't necessarily get stored/used anywhere) has a bit less to recommend it. It's also slightly confused conceptually to put "fail" in the Monad typeclass but that mostly bugs the theorists. The biggest downside, though, is that it can often make the source of errors harder to find - cf. hunting NaNs in floating point arithmetic.
- eru 14y agoOh, we just pretend that the monad typeclass does not have "fail" in it. It's only useful for pattern match failures when using the do-syntax. I was talking more about the (Either Error) monad, which has not much to do with "fail". (Edited first sentence.)
- dsymonds 14y agoThe big problem with errno is that it's a global: there's exactly one of them for your program. As soon as you start using threads you are screwed. Go aims to make concurrency right. That means you can depend on a global variable for propagating error information. errno in C is ultimately because C doesn't have multiple return values, and the alternatives (pointer arguments as implicit output) are sucky. Go has multiple return values, and so returning an error is natural.
- fafner 14y agoerrno is thread locale in most modern implementations
- Erwin 14y agoerrno is generally defined to be a macro like: (errno_location()), at least when you use the -pthread option with gcc. The odd constructoin (the function returning an int which is dereferenced) allows the errno macro to work as an "lvalue", i.e. that you can still say "errno = 0" to clear its value.
- Jabbles 14y agoThe practical difference is that there is (partial) enforced checking of errors. If you do not wish to check the error code, you must be explicit. f := os.Create("output.txt") Will give a compile error multiple-value os.Create() in single-value context: and should be written as one of these: f, err := os.Create("output.txt") f, _ := os.Create("output.txt") where the second indicates that you are ignoring the returned error. I say "partial" because if you ignore all the return values you can implicitly ignore an error: fmt.Println("Hello") It is quite common to see: if err != nil { //handle error } in Go, as their philosophy is that the caller should handle the error, not an unrelated piece of code. Yes, this does sometimes look like "litter", and it doesn't really make for the most impressive code. However, you won't be surprised by it, it's simple to understand, and if something goes wrong, you'll know what, when, and what was done about it. This has been Google's opinion on C++ for a while. http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Exceptions#Exceptions http://google-styleguide.googlecode.com/svn/trunk/cppguide.x... lists some pros and cons. Writing code that ignores errors is one way of writing code, I think it's idiomatic Java and Python (please correct me), but that's not the way that was chosen for Go. I don't really use panic() except to indicate bugs: if playerCount == 2{ go multiPlayer() } else if playerCount == 1{ go singlePlayer() } else { panic(playerCount) } However they can be useful inside libraries. For example if you call fmt.Printf("%s",s), where s has a method s.String(), will print the result of s.String(). Sound good? But what if s is a nil pointer? Well, you may wish s.String() to handle that and return "A nil s", which is fair enough. However is s.String does not handle this case it may panic(), leaving fmt.Printf to recover() and write "<nil>" instead. http://golang.org/src/pkg/fmt/print.go#L630 http://golang.org/src/pkg/fmt/print.go#L630
- kstenerud 14y agoThis doesn't sit well with me. Who is to say what's catastrophic and what's not? Should I use all libraries in fear because passing a nil pointer or an improperly formatted date may cause it to terminate the program with no recourse? Suddenly every library is a ticking time bomb with a scorched Earth policy for things it doesn't like. How am I to know what's going to cause my valuable program to simply die in the middle of the night? Look at the outdated/incomplete documentation? Pore over the complex code (if it's even available)? Now not only do I need to litter error value checks everywhere, but I also need to preemptively sanitize all input to any library calls I make and pray that I haven't missed something. This is simply trading one problem for another.
- toddh 14y agoSince errno is now thread local it's not the big deal it used to be, but it still contains no data, so returning objects is superior because it can provide error context and the error information can be chained together as flows up the stack. I was horrified at go's error handling policy until I learned multiple values could be returned from functions, that gets rid of the main problem with using error returns in C.
- deleted 14y ago[deleted]
- michaelt 14y agoI'm not familiar with Go. If my code performs several operations in a row, any of which could return an error, do I have to check the returned error value after each one? For example if performing file or network I/O?
- dscrd 14y agoPerhaps depending on how important it is for that piece of code to be flawless, but generally yes. The upside of all that is that you can craft a piece of code to be practically flawless, since you know how the libraries you are using are supposed to break if they break.
- mononcqc 14y agothe timeline C -> C++ -> Java -> Go is ridiculous at best, and this ignores work on exceptions or error handling in countless other languages that could have made it better.
- batista 14y agoWrong, the timeline C -> C++ -> Java -> Go is more or less what the industry went through (well, assuming it's getting to Go now and it'll catch on). It correctly ignores the work on "countless other languages", because they didn't have any if at all traction to the trade, and don't matter much as far as 90% of programmers are concerned, and the few languages outside those that do (Perl, PHP, Ruby, C#, JS, Python, VB) are quite similar to the above.
- taylonr 14y agoThis approach made me think of MFC calls. Instead of having multiple return values you'd have a single return value and multiple out parameters. It's been years since I've done any MFC work, but calls like: HWND myWin = GetTopWindowTitle(region, out title, out size, out whateverElse); Then you'd have to either assume that myWin was not an error code and just use the out parameters, or you'd explicitly check the myWin value each time.
- fafner 14y agoI think the language that gets Exceptions right is Common Lisp because of the restarts. With other languages handling exceptions is complicated. You either need to know internals of the code you are using or you simply bail out and print and error. With restarts you can simply offer ways to fix it and keep the logic where it belongs, close to the "throw".
- bitbiter 14y agoThe obvious problem with the multiple return paradigm is that Go returns: result AND error whereas it would make much more sense to return: result OR error After all, if the error is true the result is invalid. This is one thing exceptions basically gets right. Go, much like the errno paradigm, relies on the programmer's diligence to know when it is safe to use the result. A better designed language takes away the option of accidentally doing something wrong. Besides exceptions, this better approach can be accomplished by returning a union type variable that knows its current type (languages outside the C lineage call this sum types or tagged unions). var foo = Bar(); if( typeof(foo) == Error ) { // handle error } else { // result is safe to use }
- TillE 14y agoConceptually that makes sense, but having to explicitly check the type of the return value every time is just hideous.
- pacala 14y agoUhm, then don't code in Go?
- sirclueless 14y agoYou can ignore errors in Go by assigning them to _. You can't if your return value is sometimes an Error.
- ori_b 14y agoYou could implement something like lvalue pattern matching, if you wanted to. Assuming you construct tagged unions like so: Valid 123 Error "Error message" You could pattern match on the left side of an assignment like so: Valid foo = dosomething() use(foo) Which would extract the wrapped value into 'foo' if the union's tag was 'Valid', or aborted/panicked/did something else somewhat sane if the value didn't match.
- 14y ago
- pacala 14y agoI fail to see how this is any better or worse than Java checked exceptions. You've got an error, you return an alternate value, which is handled by a different part of the code. Here is a different syntax to semantically write the exact same code, it's obviously superior.
- TylerE 14y agoThe difference is that the Go style can provide a sane fallback, which the client code can IGNORE. For instance, the built in map type returns a pair of values, the first is the actual matched value, the 2nd a boolean. If a key is not found the value is set to the type's zero-value, and the boolean as false. So for a map of string->integer, instead of throwing a NoSuchKey exception or something, just returns 0,false. So there's no mandatory ceremony when you don't care.
- barrkel 14y agoNo library in a language with exceptions as they're normally used would do that. Exceptions are for exceptional cases. You use the method that throws only if it is a programming error for it to throw; and you do not, as a general rule, catch exceptions. See eg Dictionary.TryGetValue in .net. Your argument is against a straw man, IMHO.
- TylerE 14y agoSo are you saying python is not a sane language? >>> a = {} >>> a['foo'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'foo'
- btilly 14y agoThe big complication in Go that makes exceptions tricky is, "Who is my real caller?" If you're in a goroutine, is your caller the place your goroutine was launched from, or whoever is hooked up at the other end of the channel that is expecting you to respond? In some sense it is both. Where would you propagate an exception? How do you choose? Returning exception values provides an answer - it is up to you.
- mononcqc 14y agoSee links and monitors in Erlang as another way to do it, tried and tested for decades in production code.
- ericmoritz 14y agoErlang's error handling is very well thought out. In fact, error handling is the main reason Erlang is shaped the way it is. It is a combination of Go's technique but with panics for exceptional errors. Like Go, functions should return error values for known errors. For instance, `file:open/2` returns `{error, enoent | eacces | eisdir | enotdir | enospc}` for known error conditions that can be handled. Unknown, exceptional conditions, like a NAS going down, can't be compensated for because the set of unknown errors is unbounded. There could literally be an infinite number of things that can go wrong that you don't know how to handle. For try/catch style error handling, the only safe way to handle the unknown errors is to do a Pokemon catch clause globally or around every single potentially error causing function call. Because of the fact that you can't stop errors from happening is why Erlang has processes. When an unknown error occurs, the individual process dies and anyone monitoring that process is notified. OTP has supervisors whose sole job is to watch over processes and restart them if they crash. Any state that needs to be preserved between restarts is stored somewhere safe (Erlangers calls this the "error kernel") so that when the process comes back it'll resume where it left off. Once you structure your code to "Let it Crash", your code gets much simpler and safer. #1, the error return values forces you to compensate for known errors that can happen with a function, #2 you have to think about data durability in order to "Let it Crash". #3 Supervisors take care of the rest. Data durability is made much easier with the fact that individual processes are single threaded and data is immutable, this makes them transactional by nature.
- antonpug 14y agoAm I the only one who is saying "What the heck is Go and where did it come from?!" All of a sudden, in the past month or two I am seeing a lot of talk about Go, out of thin air. What's the rage about? Is it really going to take over the world?!
- genwin 14y agoResistance is futile! It is a slick language. It hit version 1.0 in March, which is probably why you're hearing so much about it now. Its concurrency model lets you keep all the machine's processor cores busy, without you going insane; that's worth the price of admission for me.
- antonpug 14y agoDo you see any use for Go in Web Development?
- genwin 14y agoI'm coding a web app in it right now. Go includes a package for its own web server. That's a huge benefit, because it means the app can be state-ful instead of the usual (for Python, PHP etc.) stateless. The former means you can have all the data you want ready to go, in the most efficient memory structure, when the user arrives. Which translates to great performance. For example check out http://langalot.com http://langalot.com and somewhat slowly type in "bark".
- antonpug 14y agoDo you see it as a competitor technology to things like Node.js / Meteor?
- genwin 14y agoYes. Node.js supports only a single processor core. I don't know much about Meteor but it doesn't look like it offers concurrency features. The concurrency in Go (and to a lesser extent in Node.js--probably) is amazingly performant. For example, when I poll & parse 100 RSS feeds and the slowest one takes 6 seconds to return from the remote site, the whole routine returns in < 7 seconds. A synchronous single-core app would take up to 6 seconds * 100 = 10 minutes for the same process. In Go, when I call out to the remote sites, each call returns immediately, so that all 100 remote calls finish immediately. Go puts my routine into a dormant state while waiting for the remote sites to return data. So during that 7 seconds my app may have ~5 seconds of dormancy to do other processing (in a completely independent code path), again using all the machine's cores.
- seanalltogether 14y agoI would love to see some code to illustrate this article, as someone who hasn't written anything in Go I still don't know what these mechanisms look like in practice.
- genwin 14y agoHere's some relevant code: http://blog.golang.org/2010/08/defer-panic-and-recover.html http://blog.golang.org/2010/08/defer-panic-and-recover.html
- dllthomas 14y agoI find it interesting that they express result and error as a product rather than a sum.
- usefulcat 14y agoThe primary question to answer when choosing between using error codes or exceptions for signalling errors is this: do you want the error propagation code to be written by one or more humans, or do you want the compiler to generate invisible but consistent error propagation code?
- eldude 14y agoThis is a lot like how node.js handles Errors and exceptions only its "panic" call is throwing an Error either intentionally or via an uncaught exception, which will crash the process and is difficult to catch due to node.js' asynchronous nature. Apart from that, every function call returns both an error and a value, and the value is irrelevant in the presence of an Error.
- cpeterso 14y agoGo's error handling reminds me of the Icon programming language's elegant "goal-directed execution". Icon's control structures use implicit "success/failure" instead of true/false boolean logic. For example: if a := read() then write(a) read() could return a false or zero value and still "succeed", calling write(a). If read() returns "failure", then write(a) will not be called. (I don't recall what the value of `a` will be in that case.) https://en.wikipedia.org/wiki/Icon_programming_language#Goal-directed_execution https://en.wikipedia.org/wiki/Icon_programming_language#Goal...
- Evbn 14y agoThat is like return codes in shell.