Understanding the Error
A Go panic runtime error crashes your program unexpectedly. The error typically looks like this:
``` panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x483c5d]
goroutine 1 [running]: main.processData(0x0, 0x0) /app/main.go:42 +0x3d main.main() /app/main.go:18 +0x85 ```
The panic message tells you exactly where the crash occurred (file and line number), but understanding why it happened requires deeper investigation.
Common Causes
1. Nil Pointer Dereference
The most frequent cause. You're calling a method or accessing a field on a nil pointer.
Problematic code: ```go type User struct { Name string Email string }
func getUser(id int) *User { // Returns nil when user not found return nil }
func main() { user := getUser(123) fmt.Println(user.Name) // PANIC: user is nil } ```
Fixed code:
``go
func main() {
user := getUser(123)
if user == nil {
fmt.Println("User not found")
return
}
fmt.Println(user.Name)
}
2. Array/Slice Index Out of Bounds
Accessing an index that doesn't exist triggers a panic.
Problematic code:
``go
items := []string{"a", "b", "c"}
fmt.Println(items[5]) // PANIC: index out of range
Fixed code:
``go
items := []string{"a", "b", "c"}
if len(items) > 5 {
fmt.Println(items[5])
} else {
fmt.Println("Index out of bounds")
}
3. Map Access Without Checking
Reading from a map key that doesn't exist returns the zero value, but can lead to issues.
Problematic code:
``go
config := map[string]string{}
port := config["port"]
// port is empty string, might cause issues downstream
num, _ := strconv.Atoi(port) // Returns 0, may cause logic errors
Fixed code:
``go
config := map[string]string{}
port, exists := config["port"]
if !exists {
port = "8080" // default
}
num, err := strconv.Atoi(port)
if err != nil {
log.Fatalf("Invalid port: %v", err)
}
Diagnosis Steps
Step 1: Capture the Stack Trace
When a panic occurs in production, ensure you have proper logging:
defer func() {
if r := recover(); r != nil {
log.Printf("Panic recovered: %v", r)
debug.PrintStack()
}
}()Step 2: Enable Core Dumps (Linux/Unix)
ulimit -c unlimitedThen analyze with delve debugger:
dlv core ./your-app coreStep 3: Add Debugging Output
Insert strategic print statements before the panic location:
log.Printf("Variable state before operation: %+v", myVar)
log.Printf("Slice length: %d, accessing index: %d", len(items), index)Step 4: Use Go Race Detector
Some panics are caused by race conditions:
go run -race main.goProduction Recovery Pattern
Implement graceful recovery in HTTP servers:
func recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic recovered in %s %s: %v", r.Method, r.URL.Path, err)
debug.PrintStack()
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}Apply to your router:
r := mux.NewRouter()
r.Use(recoveryMiddleware)Verification
After applying fixes, verify with:
```bash # Run tests go test -v ./...
# Run with race detector go run -race main.go
# Run benchmarks to catch edge cases go test -bench=. -count=100 ```
Prevention Strategies
- 1.Use linters: Install and run static analysis tools
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...- 1.Enable nil checks in CI: Use
nilawayfrom Uber
go install go.uber.org/nilaway/cmd/nilaway@latest
nilaway ./...- 1.Write defensive code: Always check for nil and bounds
func safeAccess[T any](slice []T, index int) (T, bool) {
var zero T
if index < 0 || index >= len(slice) {
return zero, false
}
return slice[index], true
}