Errors · 3 MIN READ
panic is for bugs, not for failures
Say which of two situations deserves a panic.
A file that will not open is a failure: it is expected, the caller can do something about it, and it is an error value. An index past the end of a slice is a bug: the program's own logic is wrong, and continuing would be worse than stopping. Go draws that line deliberately, and a library that panics on bad input has drawn it in the wrong place.
recover exists, and its honest use is narrow: stopping one request from taking down a server, at the boundary, before returning an ordinary error. It is not a catch block, and using it as one hides the bug that caused it.
SEE THE IDEA
Ordinary failures and broken invariants have different exits
- 01
Expected failure
goreturn errThe caller decides how to handle it
- 02
Broken invariant
gopanic("invalid state")Stops normal execution in this goroutine
- 03
Cleanup
godefer cleanup()Runs while the stack unwinds
Panic is an exceptional control flow, not a replacement for an error result.
In practice
Return errors for invalid input, missing files and canceled operations that callers can reasonably expect. Reserve panic for contracts that cannot continue safely, or for a deliberate internal boundary that will recover. An unrecovered panic terminates the program.
Watch out
A deferred recover in one goroutine cannot catch a panic that happens in another goroutine.
Read and predict
package main
import "fmt"
func safe(work func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
work()
return nil
}
func main() {
fmt.Println(safe(func() { fmt.Println("fine") }))
fmt.Println(safe(func() { panic("bad state") }))
}What does this print? Note that err is a named return.
Trace each value and write down your reasoning. Copy the source into GopherForge or your local Go environment to compile and check it. This web page does not execute your code or send it to a compiler service.
Progress stays in this browser. No account needed.