Defer, Panic, and Recover
Go provides three built-in mechanisms for controlling unusual execution flow: defer for cleanup, panic for unrecoverable situations, and recover for catching panics at a boundary.
defer
defer schedules a function call to run just before the surrounding function returns. Deferred calls execute even if the function panics:
package main
import "fmt"
func greet(name string) {
defer fmt.Println("goodbye,", name)
fmt.Println("hello,", name)
}
func main() {
greet("Alice")
// Output:
// hello, Alice
// goodbye, Alice
}Typical Use: Resource Cleanup
The idiom is to open a resource and immediately defer its close:
package main
import (
"fmt"
"os"
)
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close() // always runs when readFile returns
buf := make([]byte, 256)
n, _ := f.Read(buf)
return string(buf[:n]), nil
}
func main() {
content, err := readFile("/etc/hostname")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Print(content)
}LIFO Ordering
Multiple deferred calls execute in last-in, first-out order:
package main
import "fmt"
func main() {
defer fmt.Println("third")
defer fmt.Println("second")
defer fmt.Println("first")
fmt.Println("start")
// Output:
// start
// first
// second
// third
}Deferred Function Arguments Are Evaluated Immediately
The arguments to a deferred function are evaluated when the defer statement is reached, not when the deferred function runs:
package main
import "fmt"
func main() {
x := 10
defer fmt.Println("deferred x:", x) // captures x=10 now
x = 20
fmt.Println("current x:", x)
// Output:
// current x: 20
// deferred x: 10
}panic
panic stops normal execution and begins unwinding the call stack. All deferred functions run during unwinding before the program terminates:
package main
import "fmt"
func mustPositive(n int) int {
if n <= 0 {
panic(fmt.Sprintf("expected positive, got %d", n))
}
return n
}
func main() {
fmt.Println(mustPositive(5)) // 5
fmt.Println(mustPositive(-1)) // panics
}Idiomatic Go: Reserve
panicfor truly unrecoverable programmer errors (e.g., invalid arguments to a library function). Returnerrorvalues for expected failure conditions.
recover
recover stops a panic and returns the value passed to panic. It only has an effect when called directly from a deferred function:
package main
import "fmt"
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
result = a / b // panics if b == 0
return
}
func main() {
r, err := safeDiv(10, 2)
fmt.Println(r, err) // 5 <nil>
r, err = safeDiv(10, 0)
fmt.Println(r, err) // 0 recovered: runtime error: integer divide by zero
}Middleware Pattern
A common use of recover in web servers is a top-level panic handler that prevents one request from crashing the whole server:
package main
import (
"fmt"
"net/http"
)
func recoveryMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
http.Error(w, "internal server error", http.StatusInternalServerError)
fmt.Println("recovered panic:", rec)
}
}()
next(w, r)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
panic("something went very wrong")
}
func main() {
http.HandleFunc("/", recoveryMiddleware(handler))
fmt.Println("listening on :8080")
http.ListenAndServe(":8080", nil)
}Key Takeaways
deferschedules cleanup; it always runs even when a panic occurs.- Multiple deferred calls execute in LIFO order; arguments are evaluated immediately.
panicis for unrecoverable programmer errors — returnerrorfor expected failures.recovermust be called inside a deferred function to catch a panic.- Use
recoverat process boundaries (servers, goroutine entry points) to prevent crashes.