In Go, the standard approach is error handling through the returned value of error. This allows explicit control over what to do when an error occurs at each point. Panic is a mechanism for handling unforeseen catastrophic situations: it interrupts execution until the nearest defer, where recover can be used to "rescue". However, recover only works inside defer.
Best practices:
func safeDivide(a, b int) (int, error) { if b == 0 { return 0, errors.New("divide by zero") } return a/b, nil } func mustDivide(a, b int) int { if b == 0 { panic("divide by zero!") } return a/b }
Can any panic error be "caught" using recover anywhere in the program?
Answer: No, recover will only work inside defer and only in the same goroutine where the panic was invoked. In other cases, the panic will terminate the execution of this (or all) goroutines — this often becomes unexpected.
Story
In the REST API, panic was used to handle common errors in business logic. This led to non-obvious application crashes and incorrect logs because recover did not always work correctly.
Story
In the payment processing service, defer + recover was implemented in the main function to catch all panics, but they forgot about goroutines — in child goroutines without defer/recover the application crashed with "terrible" errors, leaving some transactions in an inconsistent state.
Story
In the parser for complex structures, they forgot to return an error, replacing it with panic — this complicated support and testing, and they had to completely rewrite error handling to achieve detailed logging and correct UX.