The Secret Life of Go: Panic and Recover

 

The Secret Life of Go: Panic and Recover

Mastering Panic, Recover, and the "Must" Pattern in Go.





Chapter 21: The Emergency Brake

"I think I killed it," Ethan whispered.

He was staring at his terminal. His web server, which had been running smoothly for weeks, was gone. No logs, no shutdown message, just a sudden return to the command prompt.

"What did you do?" Eleanor asked, leaning over his shoulder.

"I just sent a POST request with an empty JSON body," Ethan said. "I expected a 400 Bad Request error. Instead, the whole server vanished."

"Show me the logs," Eleanor said.

Ethan scrolled up.

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a2b40]

goroutine 1 [running]:
main.HandleRequest(...)
    /server/main.go:42 +0x45
main.main()
    /server/main.go:80 +0x120

"Ah," Eleanor nodded. "You are trying to access a field on a nil pointer. In C++, this would be a segmentation fault. In Go, it is a panic."

"But I check for errors everywhere!" Ethan protested.

"A panic is not an error," Eleanor corrected. "An error is a polite knock at the door: 'Excuse me, I couldn't open this file.' A panic is a fire alarm: 'EVERYTHING IS BURNING.' It stops execution immediately, runs your deferred functions, and then crashes the program."

The Unwinding

"So... game over?" Ethan asked.

"Not necessarily," Eleanor said. "When a panic happens, it bubbles up through the stack, function by function. It is like an elevator free-falling down a shaft."

She sketched a diagram:

  1. HandleRequest panics! -> CRASH
  2. ServeHTTP stops -> CRASH
  3. main stops -> EXIT PROGRAM

"We need emergency brakes," Eleanor said. "We need a mechanism to stop the free-fall before it hits the bottom and kills the process."

Containing the Blast (Recover)

"In Chapter 20, we learned about defer," she continued. "That is the only place where you can stop a panic."

"Why only there?"

"Because when a function is panicking, it skips all normal code. It only runs deferred functions. If you want to catch the panic, you have to be waiting in the defer stack."

She opened a new file to write a Middleware.

func RecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

        defer func() {
            if err := recover(); err != nil {
                // The Safety Net caught something!
                log.Printf("Panic recovered: %v", err)
                w.WriteHeader(http.StatusInternalServerError)
            }
        }()

        next.ServeHTTP(w, r)
    })
}

"The recover() function is magic," Eleanor explained. "If the program is panicking, recover() catches the panic value and stops the crashing elevator. It returns control to you. If the program is not panicking, it just returns nil and does nothing."

"So it converts a crash into a controlled landing?"

"Exactly. It contains the blast radius. One bad request dies, but the server lives on."

Ethan updated his main function.

func main() {
    handler := http.HandlerFunc(HandleRequest)
    safeHandler := RecoveryMiddleware(handler) // Wrap it in the safety net
    http.ListenAndServe(":8080", safeHandler)
}

He ran the server and sent the "poison" request again.

  • Terminal: Panic recovered: runtime error: invalid memory address or nil pointer dereference
  • Server Status: Still Running.

"It didn't crash!" Ethan cheered.

The Rule of Law

"Now," Eleanor said, her voice dropping to a serious tone. "You know how to use panic and recover. My advice to you is: Don't."

Ethan blinked. "What?"

"Do not use panic for normal error handling," she warned. "If a user file is missing, return an error. If the database is down, return an error."

"So when do I use it?"

"Only for the impossible," Eleanor said. "Use it when the programmer has made a mistake so severe that the code cannot logically continue. Or during startup, if a required configuration is missing."

func MustLoadConfig() *Config {
    cfg, err := load("config.yaml")
    if err != nil {
        panic("cannot load config: " + err.Error()) 
        // Acceptable: The app literally cannot start without this.
    }
    return cfg
}

"We use recover at the boundary of our system—like in this middleware—to prevent a single buggy request from taking down the whole ship. But inside your business logic? Stick to errors."

She paused, adding one final warning. "And remember: recover only works in the same goroutine. If you spawn a new background worker and it panics, this middleware won't save you. The whole program will crash."

Ethan looked at his code. "So... panic is for crashes, errors are for problems."

"Precisely," Eleanor smiled. "And recover is the parachute that ensures you live to debug another day."


Key Concepts from Chapter 21

Panic:
A built-in function that stops the ordinary flow of control.

  • Effect: It stops the current function, runs any deferred functions, and then crashes the program (unless recovered).
  • Cause: Runtime errors (nil pointer dereference, index out of range) or explicit panic() calls.

Recover:
A built-in function that regains control of a panicking goroutine.

  • Requirement: Must be called inside a deferred function.
  • Behavior: If called during a panic, it captures the value and stops the crash. If called normally, it returns nil.

The "Must" Pattern:
It is idiomatic in Go to panic during initialization if a required resource fails (e.g., regexp.MustCompile or loading config). If the app can't run, it should crash early.

Goroutine Boundaries:
panic only bubbles up the stack of the current goroutine.

  • If you spawn a generic go func(), and it panics, a recover in main will not catch it. The program will crash.

Next chapter: The Context Package. Ethan learns how to cancel a request that is taking too long.


Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.

Comments

Popular posts from this blog

The New ChatGPT Reason Feature: What It Is and Why You Should Use It

Insight: The Great Minimal OS Showdown—DietPi vs Raspberry Pi OS Lite

Raspberry Pi Connect vs. RealVNC: A Comprehensive Comparison