That's odd. It's clearly a system like exception-throwing, but without the ability to distinguish from different kinds of errors (say, one might be able to recover from "received malformed input" but not from "array index out of bounds"), and still requiring that programmers check the error return value of most functions they call.
I wonder why they preferred that approach over the Java/Python approach to exceptions.
Go emphasizes (and makes easy) a deliberately inline way of handling expected errors. So panics are for unexpected, normally non-recoverable errors. Recover allows one portion of code to be sandboxed against panics in another. Sandboxes are the atypical case.
What this means is that you can look at arbitrary Go code and know that it contains no "action at a distance". It exits where it says "return". Control flow is explicit.
Contrast Java, where I can't see what will throw unless I pull the code up in Eclipse and use "mark occurrences" feature on the declared exception - and it can be surprising. Oh and also, there could be exceptions thrown and not declared, if they are "runtime". Even Eclipse doesn't help much there.
Panic in Go is closer to Java's Error. Catching Error is usually a bad idea.
There's a number of programmers who really seem to dislike exceptions: Joel Spolsky and Rob Pike among them.
This discussion always seems really weird to me, because I've been using exceptions for about 15–20 years now, and my experience has always been: 1) my code looks much nicer, 2) my code is far easier to make correct, and 3) exceptions give me no trouble whatsoever. Honestly, I experience 10x as much pain from the _for_ loop as I do from exceptions, for crying out load!
Maybe if I had spent more of my like working with particularly ugly Java systems, then I might feel differently. But when using C++, Lisp, Dylan, Schema, Python and Ruby, exception handling has always been completely painless. Well, in C++, you need to use RAII, and sometimes you need to write WITH-OPEN-FILE or File.open {|f| ... }. But this is an order of magnitude easier than making explicit error-handling correct.
In fairness to Go, exception handling is much trickier when you live in a world of lightweight co-routines. But again, Erlang seems to generalize things in a plausible way: Declare links between co-operating processes, and propagate failures across those links until somebody wants to catch them and recover.
It looks like Go is slowly moving in that direction. But in the meantime, writing Go programs always leaves a sour taste in my mouth—I really don't want error-handling code gunking up my programs like that.
Also Haskell seems to discourage exceptions. They are possible to raise everywhere and catchable in the IO Monad --- but they do not play along nicely. It's often better to wrap up your error handling in a Maybe or Either or Error Monad. (In level of increasing sophistication.)
I guess you can inspect the value given to panic to distinguish between errors, but without standard types, it would be ad hoc and limit library interoperability.
I do find it interesting, however, that the manual error propagation scheme is functionally identical to simple exception handling when followed rigorously. Without standard error types it has similar problems; pattern matching of some kind wouldn't go amiss.
The alleged advantage of using error codes (aside from explicit code flow, already hurt by panic) over exceptions is similar to Java's mistake of exception specifications, encouraging people to deal with errors at a low level (usually wrong IMHO) and making it a pain to pass the unobfuscated error up the stack for logging / user display.
The same problem applies to error codes; meanwhile, careful and consistent propagation of error codes to each caller in turn is semantically equivalent to a panic or an exception unwind, just more laborious, and a smidgen easier to ignore. Indeed, the ease of ignoring errors seems to be one of the motivations of Go's approach, almost as if they got bit by Java's dumb exception specification approach, but learned the wrong lesson - rather than making exceptions unchecked, instead making it easier to insert the infamous empty catch {} (i.e. assign ignored error to _).
The problem is at its heart the notion of an exceptional situation. Whether a condition met by a library is exceptional usually depends on how it is used by its caller, which may be many stack frames away. The library either needs to ask its caller if it should really ignore this error (which probably came from another library again, maybe file I/O or network, etc.). The usual alternate choices are unilateral abortion (raise an exception, or conscientiously propagate an error code) and unilateral ignoring (which is the alternative Go seems to be trying to offer). If Go really wanted to enable handling problems at this low level rather than propagating to higher levels like exceptions do, it might have implemented a restartable condition system; this would have let the library give the higher level module the choice. But Go didn't do that.
"We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional."
To me, that comes across as "We don't like traditional exception handling because languages that use exceptions for error-handling encourage programmers to use exceptions for error-handling." Well, yes, that's what they're for.
Python's exception system is far from perfect - it would be nice if there were a standard way to annotate exceptions, and if exceptions weren't quite so inefficient - but it's much easier to design or learn an API when there's one error-handling system consistently applied, instead of two independent ones.
Actually, you can recover from specific types of errors. Check the value returned from recover(), and if it's not what you want to handle, panic(value).
I wonder why they preferred that approach over the Java/Python approach to exceptions.