14

Why did Go end up adopting exception handling with panic/recover, when the language is so idiomatic and a strong advocate of error codes? What scenarios did the designers of Go envision not handled by error codes and necessitate panic/recover?

I understand convention says limit panic/recover, but does the runtime also limit them in ways that they can't be used as general throw/catch in C++?

icza
  • 389,944
  • 63
  • 907
  • 827
zlatanski
  • 815
  • 1
  • 8
  • 13
  • 3
    I have both upvoted your question and voted for it to be closed: while sensible and interesting, it breaks one of the SO's guidelines--that questions should not provoke answers which will be primarily opinion-based. I'd recommend you to ask questions of this style on the [Go mailing list](https://groups.google.com/forum/#!forum/golang-nuts/) instead. – kostix Feb 15 '16 at 15:36
  • 2
    Though if we replace "Why did Go add..." with "What is the use of...", it becomes an objectively answerable question. – biziclop Feb 15 '16 at 15:37
  • 3
    @zlatanski, what you ignore is: 1) SO is not the sole medium in the world to get knowledge on programming topics. It was designed with a set of goals clearly stated in its FAQ. The fact you have decided you do not like these goals is your problem, not that of SO; 2) That's you who decided to break the rules. Finger-pointing at me is hence surprising: suppose you moved from US to England but decided to still drive on the right part of the road; will you blame the cop which will stop you? – kostix Feb 15 '16 at 16:12

4 Answers4

18

Some history:

In the early days of Go (before version 1.0) there was no recover(). A call to panic() would terminate an application without any way to stop that.

I've found the original discussion that led to adding recover(), you can read it on the golang-nuts discussion forum:

Proposal for an exception-like mechanism

Beware: the discussion dates back to March, 25, 2010, and it is quite exhausting and long (150 posts through 6 pages).

Eventually it was added on 2010-03-30:

This release contains three language changes:

  1. The functions panic and recover, intended for reporting and recovering from failure, have been added to the spec:
    http://golang.org/doc/go_spec.html#Handling_panics
    In a related change, panicln is gone, and panic is now a single-argument function. Panic and recover are recognized by the gc compilers but the new behavior is not yet implemented.

Multi-return values and conventions provide a cleaner way to handle errors in Go.

That does not mean however that in some (rare) cases the panic-recover is not useful.

Quoting from the official FAQ: Why does Go not have exceptions?

Go also has a couple of built-in functions to signal and recover from truly exceptional conditions. The recovery mechanism is executed only as part of a function's state being torn down after an error, which is sufficient to handle catastrophe but requires no extra control structures and, when used well, can result in clean error-handling code.

Here is a "real-life" example for when/how it can be useful: quoting from blog post Defer, Panic and Recover:

For a real-world example of panic and recover, see the json package from the Go standard library. It decodes JSON-encoded data with a set of recursive functions. When malformed JSON is encountered, the parser calls panic to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the 'error' and 'unmarshal' methods of the decodeState type in decode.go).

Another example is when you write code (e.g. package) which calls a user-supplied function. You can't trust the provided function that it won't panic. One way is not to deal with it (let the panic wind up), or you may choose to "protect" your code by recovering from those panics. A good example of this is the http server provided in the standard library: you are the one providing functions that the server will call (the handlers or handler functions), and if your handlers panic, the server will recover from those panics and not let your complete application die.

How you should use them:

The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.

Related and useful readings:

http://blog.golang.org/defer-panic-and-recover

http://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right

https://golang.org/doc/faq#exceptions

http://evanfarrer.blogspot.co.uk/2012/05/go-programming-language-has-exceptions.html

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks for answer. I did read FAQ and blog. They conflict. FAQ say "truly exceptional conditions". Blog say "malformed JSON is encountered... unwind stack" so basically uses it for control flow on bad JSON (which does not strike me as "truly exceptional condition" like some unexpected OS error, just user input error) – zlatanski Feb 15 '16 at 15:49
  • 2
    @zlatanski, no, using `panic` for the case you're referring to is clearly *abusing* the feature. But the thing is, abusing it that way makes code more readable/understandable and maintainable than checking flags etc. It's better to give up some purity and win in maintainability than the other way around ;-) – kostix Feb 15 '16 at 15:54
  • 3
    Thanks for digging up the history too, very insightful – zlatanski Feb 15 '16 at 15:58
1

I think that your question is the result of a mental model you maintain which is instilled by popular mainstream languages such as Java, C++, C#, PHP and zillions of other which simply got exceptions wrong.

The thing is, exceptions per se are not a wrong concept but abusing them to handle cases which are, in reality, not exceptional is. My personal pet peeve is the filesystem handling API of Java (and .NET, which copied that of Java almost verbatim): why on Earth failure to open a file results in an exception if that file does not exist? The filesystem is an inherently racy medium, and is specified to be racy, so the only correct way to ensure a file exists before opening it for reading is to just open it and then check for "file does not exist" error: the case of the file not existing is not exceptional at all.

Hence Go clearly separates exceptional cases from plain normal errors. The motto of the stance Go maintains on handling errors is "Errors are values" and thus normal expected errors are handled as values, and panic() serves to handle exceptional cases. A good simple example:

  • An attempt to dereference a nil pointer results in a panic.

    The rationale: your code went on and tried to dereference a pointer which does not point to any value. The immediately following code clearly expects that value to be available—as the result of the dereferencing operation. Hence the control flow clearly can't proceed normally in any sensible way, and that's why this is an exceptional situation: in a correct program, dereferencing of nil pointers cannot occur.

  • The remote end of a TCP stream abruptly closed its side of the stream and the next attempt to read from it resulted in an error.

    That's pretty normal situation: one cannot sensbily expect a TCP session to be rock-solid: network outages, packet drops, unexpected power blackouts do occur, and we have to be prepared for unexpected stream closures by our remote peers.

A minor twist to panic() is that Go does not force you to blindly follow certain dogmas, and you can freely "abuse" panic/recover in tightly-controlled specific cases such as breaking out from a deeply-nested processing loop by panicking with an error value of a specific type known and checked for at the site performing recover.

Further reading:

Community
  • 1
  • 1
kostix
  • 51,517
  • 14
  • 93
  • 176
  • 5
    `Hence Go clearly separates exceptional cases from plain normal errors`, except there is no clear distinction between them and it entirely depends on the context for almost all cases. Hence why many languages chose exceptions as universal error handling mechanism without making artificial separation. Like it or not, it works and has it's own very big advantage - clear separation of error handling and actual business logic. Something that Go cannot do by definition - all Go code is a mess of if's and some logic inbetween. It works in simple cases but gets unreadable very quickly. – creker Feb 15 '16 at 15:54
  • @creker, basically that's why I voted to close the answer: people *do* have different opinions regarding these matters. I know I had my own "wow!" reaction when reading my first book on C++ but 15 years in the industry which followed changed my mind. I don't think I could sum my concerns up better than the author of 0MQ whose post I linked to so I invite you to read it. But sure your approaches and priorities and taste could certainly differ from mine. That's why we all love different languages after all ;-) – kostix Feb 15 '16 at 15:58
  • @creker, I know my response is a bit glib but the format of the medium we use (SO comments) is unfit for detailed discussions with examples and cases. So if you feel like it, I think we could have a constructive discussion on the mailing list I suggested the OP to use. – kostix Feb 15 '16 at 16:02
  • the sad thing is that both ways (C#/Java and Go. C++ exceptions are bad without exceptions) are good in different cases.. We need something different. Maybe Rust could be a solution. – creker Feb 15 '16 at 16:06
  • @creker Note that Java use exceptions universally, but it still has a hierarchy https://www.javamex.com/tutorials/exceptions/exceptions_hierarchy.shtml This allows to separates exceptional cases (RuntimeException) from plain normal errors. – dolmen Jul 19 '18 at 13:28
  • "got exceptions wrong" is a pretty silly statement. Returning an error value or throwing an exception is actually the same thing. The only difference is: "returning a value" returns just one function while "throwing an exception" returns more than one function at once. – ceving Feb 21 '23 at 12:15
  • @ceving, I beg to disagree. Throwing an exception is creating an alternate return path in a function, and since in the languages I referred to anything you call can throw (and in C++ _literally_ anything can throw, including operators–you do not even need to make a call) these alternate return paths get untractable. So the difference you highlight do exist but it's beside the point I was trying to convey. IMO, the first essay I referred to at the bottom of my answer has a pretty good rundown of exceptions, and it's not made by some sort of an anti-cpp zealot which adds to its value in my eyes. – kostix Feb 21 '23 at 12:51
  • @kostix There exists just one return path in computing and it is called "jump", which means modify the instruction register. – ceving Feb 21 '23 at 13:38
  • @ceving, this polemic technique is called "reductio ad absurdum". It's sad you've decided to resort to it but it's your choice. Have fun, cheers. – kostix Feb 21 '23 at 13:49
0

I think the reason is concurrency model. Go is highly concurrent language in nature and core syntax. Scenario when some computation localized to concurrent process failure, but hole system continue to work can be viewed as normal. To my mind panic and recover are about failure handling, not exceptions.

Uvelichitel
  • 8,220
  • 1
  • 19
  • 36
0

Because this is more powerful as it allows to create more resilient programs.

In C you can't recover from a NULL pointer dereference.

In Go you can catch it with recover.

This allows for example to create in Go an HTTP server that can catch a NULL pointer dereference when handling a request and return an error 500 instead of crashing the whole server.

dolmen
  • 8,126
  • 5
  • 40
  • 42