Error Handling the Go Way
Errors are values
Go has no try/catch. A function that can fail returns an error as its last value; nil means success. You check it right where it happens:
f, err := os.Open("data.txt")
if err != nil {
return err // handle or pass it up
}
defer f.Close()
// ...use f...
This if err != nil pattern appears everywhere in Go. It is verbose, but it makes every failure path visible – you cannot accidentally ignore an error.
Creating and wrapping errors
// a simple error
return fmt.Errorf("user %d not found", id)
// wrap an error with context (%w keeps the original)
if err != nil {
return fmt.Errorf("loading config: %w", err)
}
Wrapping with %w adds context while preserving the underlying error, so callers can still inspect it with errors.Is and errors.As.
defer for cleanup
defer schedules a call to run when the surrounding function returns, no matter how. It is Go's answer to finally and guarantees resources are released:
func process() error {
conn := connect()
defer conn.Close() // always runs on return
// ...
return nil
}
panic and recover (rarely)
panic is for truly unrecoverable situations (programmer bugs), not ordinary errors. It unwinds the stack and, unless recovered, crashes the program. Idiomatic Go handles expected failures with returned errors and reserves panic for the exceptional.
Key points
- Errors are returned values; check them with
if err != nil. - Create errors with
fmt.Errorf; wrap with%wto keep context. deferguarantees cleanup runs when a function returns.- Reserve
panic/recoverfor truly exceptional, unrecoverable cases.