defer is the standard way to release resources, and knowing panic/recover tells you when not to use them.
A defer statement schedules a call to run when the surrounding function returns — normally, early, or by panic. Put it right after acquiring a resource so cleanup can't be forgotten:
f, err := os.Open(name)
if err != nil { return err }
defer f.Close()Rules worth knowing:
- Deferred calls run in last-in, first-out order.
- The arguments are evaluated when the defer statement runs, not when the call happens:
defer fmt.Println(i)prints the valueihad at that moment. - Deferred closures can read and modify named results, which is how you can adjust the returned error after the fact.
- defer is per function, not per block. Deferring inside a long loop piles up calls until the function returns — move the loop body into a helper function.
A panic stops normal execution, runs the deferred calls up the stack, and crashes the program with a stack trace if nothing stops it. Runtime errors (nil dereference, index out of range) panic, and you can call panic(v) yourself — but only for truly unrecoverable programmer errors or impossible states. Expected failures are errors, not panics.
recover() called inside a deferred function stops a panic and returns its value; elsewhere it returns nil. Use it at boundaries — an HTTP server recovers per request so one bad handler doesn't kill the process — and convert the panic into an error. A panic in a goroutine can only be recovered inside that goroutine.
Example
package main
import (
"errors"
"fmt"
)
func safeDiv(a, b int) (q int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r) // set named result
}
}()
return a / b, nil
}
func main() {
for i := range 3 {
defer fmt.Print(i, " ") // runs at return: 2 1 0
}
_, err := safeDiv(1, 0)
fmt.Println(err) // recovered: runtime error: integer divide by zero
fmt.Println(errors.Unwrap(err) == nil) // true
}Remember
- defer cleanup right after acquiring the resource; runs LIFO
- Deferred call arguments are evaluated immediately
- panic for bugs, errors for expected failures; recover only in a deferred func
Watch out for
- Deferring Close inside a loop over many files — they all stay open until the function returns.