Giving up is usually the right response to a panic, but not always. It might be possible to recover in some way, or at least clean up the mess before quitting. For example, a web server that encounters an unexpected problem could close the connection rather than leave the client hanging, and during development, it might report the error to the client too.
If the built-in recover
function is called within a deferred
function and the function containing the defer
statement is
panicking, recover
ends the current state of panic and returns
the panic value.
The function that was panicking does not continue where it left off
but returns normally.
If recover
is called at any other time, it has no effect and
returns nil
.
To illustrate, consider the development of a parser for a language. Even when it appears to be working well, given the complexity of its job, bugs may still lurk in obscure corner cases. We might prefer that, instead of crashing, the parser turns these panics into ordinary parse errors, perhaps with an extra message exhorting the user to file a bug report.
func Parse(input string) (s *Syntax, err error) { defer func() { if p := recover(); p != nil { err = fmt.Errorf("internal error: %v", p) } }() // ...parser... }
The deferred function in Parse
recovers from a panic,
using the panic value to construct an error message; a fancier version
might include the entire call stack using runtime.Stack
.
The deferred function then assigns to the err
result, which is
returned to the caller.
Recovering indiscriminately from panics is a dubious practice because the state of a package’s variables after a panic is rarely well defined or documented. Perhaps a critical update to a data structure was incomplete, a file or network connection was opened but not closed, or a lock was acquired but not released. Furthermore, by replacing a crash with, say, a line in a log file, indiscriminate recovery may cause bugs to go unnoticed.
Recovering from a panic within the same package can help simplify the
handling of complex or unexpected errors, but as a general rule, you
should not attempt to recover from another package’s panic.
Public APIs should report failures as error
s.
Similarly, you should not recover from a panic that may pass through a
function you do not maintain, such as a caller-provided callback, since you
cannot reason about its safety.
For example, the net/http
package provides a web server that
dispatches incoming requests to user-provided handler functions.
Rather than let a panic in one of these handlers kill the
process, the server calls recover
, prints a stack trace, and
continues serving.
This is convenient in practice, but it does risk leaking resources or
leaving the failed handler in an unspecified state that could lead to
other problems.
For all the above reasons, it’s safest to recover selectively if at all.
In other words, recover only from panics that were intended to be
recovered from, which should be rare.
This intention can be encoded by using a distinct, unexported type for the panic
value and testing whether the value returned by recover
has that
type. (We’ll see one way to do this in the next example.)
If so, we report the panic as an ordinary error
; if not, we
call panic
with the same value to resume the state of panic.
The example below is a variation on the title
program that
reports an error if the HTML document contains multiple
<title>
elements.
If so, it aborts the recursion by calling panic
with a value of
the special type bailout
.
// soleTitle returns the text of the first non-empty title element // in doc, and an error if there was not exactly one. func soleTitle(doc *html.Node) (title string, err error) { type bailout struct{} defer func() { switch p := recover(); p { case nil: // no panic case bailout{}: // "expected" panic err = fmt.Errorf("multiple title elements") default: panic(p) // unexpected panic; carry on panicking } }() // Bail out of recursion if we find more than one non-empty title. forEachNode(doc, func(n *html.Node) { if n.Type == html.ElementNode && n.Data == "title" && n.FirstChild != nil { if title != "" { panic(bailout{}) // multiple title elements } title = n.FirstChild.Data } }, nil) if title == "" { return "", fmt.Errorf("no title element") } return title, nil }
The deferred handler function calls recover
,
checks the panic value,
and reports an ordinary error if the value was bailout{}
.
All other non-nil values indicate an unexpected panic,
in which case the handler calls panic
with that value,
undoing the effect of recover
and
resuming the original state of panic.
(This example does somewhat violate our advice about not using panics
for “expected” errors, but it provides a compact illustration of the
mechanics.)
From some conditions there is no recovery. Running out of memory, for example, causes the Go runtime to terminate the program with a fatal error.
Exercise 5.19:
Use panic
and recover
to write a function that contains
no return
statement yet returns a non-zero value.