🪂 Go’s Safety Net: Defer, Panic & Recover
Imagine you’re a tightrope walker with a safety net below. No matter what tricks you try up there, the net catches you if you fall. That’s exactly what Go gives you with defer, panic, and recover.
🎪 The Big Picture
Think of running a program like performing in a circus:
- Defer = Your cleanup crew that ALWAYS shows up after your act
- Panic = When something goes terribly wrong (you fall!)
- Recover = The safety net that catches you
Let’s meet each one!
📋 Defer Statement
What is Defer?
Defer is like making a promise: “Before I leave this room, I WILL turn off the lights.”
When you use defer, you’re telling Go: “Run this later, right before the function ends.”
func visitRoom() {
defer fmt.Println("Lights off!")
fmt.Println("Doing work...")
fmt.Println("More work...")
}
// Output:
// Doing work...
// More work...
// Lights off!
Why Use Defer?
Imagine opening a treasure chest (a file). You MUST close it when done, or bad things happen!
func readTreasure() {
file := openChest()
defer closeChest(file) // Promise made!
// Read treasure...
// Even if something breaks here,
// the chest WILL be closed!
}
Key Point: The deferred action runs even if your function crashes!
📚 Defer Stack (LIFO Order)
The Stack of Promises
What if you make multiple promises? They stack up like plates!
Last In, First Out = The last promise you make is the first one kept.
func stackExample() {
defer fmt.Println("First promise")
defer fmt.Println("Second promise")
defer fmt.Println("Third promise")
fmt.Println("Doing work!")
}
Output:
Doing work!
Third promise ← Last in
Second promise ← Second
First promise ← First in, last out
Real-World Example: Stacking Plates
graph TD A["🍽️ Put Plate 1"] --> B["🍽️ Put Plate 2"] B --> C["🍽️ Put Plate 3"] C --> D["Take Plate 3 first!"] D --> E["Take Plate 2"] E --> F["Take Plate 1 last"]
The plate you put on TOP comes off FIRST!
Arguments Are Captured Immediately
Here’s a tricky part! When you defer, the VALUES are saved right then:
func trickQuestion() {
x := 10
defer fmt.Println("x is:", x)
x = 20
fmt.Println("Changed x to 20")
}
// Output:
// Changed x to 20
// x is: 10 ← Captured when defer was called!
💥 Panic
What is Panic?
Panic is Go screaming: “EMERGENCY! I can’t continue!”
It’s like a fire alarm—everything stops, and everyone evacuates!
func ohNo() {
fmt.Println("Starting...")
panic("Something terrible happened!")
fmt.Println("This never runs")
}
When Does Panic Happen?
- You call it yourself:
panic("Oops!")
- Go panics automatically:
// Dividing by zero
result := 10 / 0 // PANIC!
// Accessing nil
var p *int
*p = 5 // PANIC!
// Out of bounds
arr := []int{1, 2, 3}
arr[100] = 5 // PANIC!
What Happens During Panic?
graph TD A["😱 Panic Occurs!"] --> B["Stop current function"] B --> C["Run all deferred functions"] C --> D["Go up to caller function"] D --> E["Run its deferred functions"] E --> F["Keep going up..."] F --> G["Program crashes with error"]
Important: Even during panic, deferred functions still run!
func panicDemo() {
defer fmt.Println("Cleanup happens!")
panic("Disaster!")
}
// Output:
// Cleanup happens! ← Defer runs first!
// panic: Disaster!
🦸 Recover
What is Recover?
Recover is your superhero! It catches panics and stops the crash.
It’s like catching a falling glass before it shatters.
Rule: recover() only works INSIDE a deferred function!
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught panic:", r)
}
}()
panic("Falling!")
// This line never runs
}
func main() {
safeFunction()
fmt.Println("Program continues!")
}
// Output:
// Caught panic: Falling!
// Program continues! ← No crash!
The Recovery Pattern
Here’s the standard pattern you’ll see everywhere:
func doRiskyThing() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
// Risky code here...
panic("boom!")
return nil
}
Recover Only Works in Defer!
This WON’T work:
func broken() {
if r := recover(); r != nil {
// This never catches anything!
}
panic("oops")
}
This WILL work:
func working() {
defer func() {
if r := recover(); r != nil {
// This catches the panic!
}
}()
panic("oops")
}
🎯 Putting It All Together
The Complete Safety Net
func riskyCircusAct() {
// Our safety net
defer func() {
if r := recover(); r != nil {
fmt.Println("🦸 Caught you!", r)
}
}()
// Cleanup promises (LIFO order)
defer fmt.Println("3️⃣ Clean equipment")
defer fmt.Println("2️⃣ Thank audience")
defer fmt.Println("1️⃣ Bow")
fmt.Println("🎪 Starting act...")
fmt.Println("🤸 Doing backflip...")
panic("😱 Lost balance!")
fmt.Println("This never happens")
}
Output:
🎪 Starting act...
🤸 Doing backflip...
1️⃣ Bow
2️⃣ Thank audience
3️⃣ Clean equipment
🦸 Caught you! 😱 Lost balance!
Flow Diagram
graph TD A["Start Function"] --> B["Set up defer with recover"] B --> C["Set up other defers"] C --> D["Run main code"] D --> E{Panic?} E -->|No| F["Normal return"] E -->|Yes| G["Run defers in reverse"] G --> H{Recover called?} H -->|Yes| I["Continue program"] H -->|No| J["Crash!"] F --> K["Run defers in reverse"] K --> L["Function ends"]
⚠️ When to Use What
| Situation | Use This |
|---|---|
| Close files/connections | defer |
| Unlock mutexes | defer |
| Something truly broken | panic |
| Web server request handler | recover |
| Library boundaries | recover |
Don’t Overuse Panic!
// ❌ BAD - Don't panic for normal errors
func findUser(id int) *User {
if id < 0 {
panic("bad id") // Too extreme!
}
}
// ✅ GOOD - Return an error instead
func findUser(id int) (*User, error) {
if id < 0 {
return nil, errors.New("bad id")
}
}
Rule of Thumb: Use panic only when your program CANNOT safely continue.
🎉 Summary
| Concept | What It Does | Analogy |
|---|---|---|
| Defer | Schedules code to run later | “Clean up after yourself” promise |
| Defer Stack | Multiple defers run in reverse | Stack of plates |
| Panic | Emergency stop | Fire alarm |
| Recover | Catches panics | Safety net |
You now have Go’s complete safety toolkit! 🛡️
Remember:
- Defer = Always clean up
- Panic = Only for emergencies
- Recover = Only inside defer
Go forth and write safe Go code! 🚀
