Master Go Error Handling: Built-in Errors, Wrapping, and Panic/Recover
This article explains Go's unique error handling approach, covering the built-in error interface, creating errors with errors.New, wrapping errors using fmt.Errorf, defining custom error types, logging errors with the log package, and managing severe failures with panic and recover, complete with practical code examples.
Go error handling overview
Unlike languages that use try‑catch, Go treats errors as ordinary return values. Every function that can fail returns an error alongside its regular result, and callers must check the error explicitly. This makes error handling explicit and improves code readability.
Built‑in error type
The error interface is defined in the standard library as:
type error interface {
Error() string
}Any type that implements Error() string satisfies the interface, allowing custom error types. The errors package provides a simple constructor:
import "errors"
err := errors.New("division by zero, please check")Typical usage checks the returned error immediately after a call.
Error wrapping (Go 1.13)
Go 1.13 introduced the %w verb in fmt.Errorf, which wraps an existing error while preserving its original value. Wrapped errors carry additional context for easier debugging.
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division error: %w", errors.New("divisor cannot be zero"))
}
return a / b, nil
}Custom error types
Developers can define structs that implement the Error() method to attach extra information such as codes, timestamps, or domain‑specific fields.
package main
import (
"fmt"
)
type FunTesterError struct {
Reason string
}
func (e *FunTesterError) Error() string {
return fmt.Sprintf("%s is fun", e.Reason)
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, &FunTesterError{Reason: "I am fun"}
}
return a / b, nil
}
func main() {
result, err := divide(8, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}The custom type implements error, so it can be returned and inspected like any other error.
Error logging
The built‑in log package provides a straightforward way to record errors, which is essential for monitoring production systems.
package main
import (
"errors"
"fmt"
"log"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("divisor cannot be zero, please check")
}
return a / b, nil
}
func main() {
result, err := divide(4, 0)
if err != nil {
log.Printf("Error: %s", err)
return
}
fmt.Println("Result:", result)
}Panic and recover
For unrecoverable situations Go provides panic, which aborts normal flow and unwinds the stack, executing any deferred functions. recover can be called inside a defer to capture the panic value and resume execution.
panic
package main
import (
"fmt"
"log"
)
func divide(a, b int) int {
if b == 0 {
panic("divisor cannot be zero, please check")
}
return a / b
}
func main() {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic: %v", r)
}
}()
fmt.Println(divide(4, 0))
fmt.Println("Program exits normally")
}Best practice
Reserve panic for truly exceptional conditions (e.g., out‑of‑bounds access, nil‑pointer dereference). Use regular error returns for expected failures. recover should be used sparingly, typically in top‑level handlers, to log the issue and perform cleanup before terminating or continuing safely.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
