Defer, Panic, and Recover
Defer, panic, and recover are Go’s mechanisms for cleanup, error signaling, and exception-like handling. Understanding these is crucial for robust Go programs.
Defer Statement
Basic Defer
package main
import "fmt"
func main() {
defer fmt.Println("Deferred 1")
defer fmt.Println("Deferred 2")
defer fmt.Println("Deferred 3")
fmt.Println("Main")
}
// Output:
// Main
// Deferred 3
// Deferred 2
// Deferred 1
Defer with Functions
package main
import "fmt"
func cleanup() {
fmt.Println("Cleaning up...")
}
func process() {
defer cleanup()
fmt.Println("Processing...")
}
func main() {
process()
}
// Output:
// Processing...
// Cleaning up...
Resource Cleanup
File Handling
package main
import (
"fmt"
"os"
)
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error:", err)
return
}
defer file.Close()
// Read file...
fmt.Println("File opened and will be closed automatically")
}
func main() {
readFile("data.txt")
}
Database Connections
package main
import "fmt"
type Database struct {
connected bool
}
func (db *Database) Connect() error {
db.connected = true
fmt.Println("Connected to database")
return nil
}
func (db *Database) Close() error {
db.connected = false
fmt.Println("Disconnected from database")
return nil
}
func queryDatabase() {
db := &Database{}
if err := db.Connect(); err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
// Query database...
fmt.Println("Querying database")
}
func main() {
queryDatabase()
}
Defer with Arguments
Argument Evaluation
package main
import "fmt"
func main() {
x := 10
defer fmt.Println("x is", x) // x evaluated now
x = 20
fmt.Println("x is", x)
}
// Output:
// x is 20
// x is 10
Defer with Named Return Values
package main
import "fmt"
func divide(a, b int) (result int, err error) {
defer func() {
fmt.Printf("Dividing %d by %d, result: %d, error: %v\n", a, b, result, err)
}()
if b == 0 {
err = fmt.Errorf("division by zero")
return
}
result = a / b
return
}
func main() {
divide(10, 2)
divide(10, 0)
}
Panic
Triggering Panic
package main
import "fmt"
func main() {
fmt.Println("Start")
panic("Something went wrong!")
fmt.Println("End") // Never reached
}
// Output:
// Start
// panic: Something went wrong!
Panic with Conditions
package main
import "fmt"
func divide(a, b int) int {
if b == 0 {
panic("division by zero")
}
return a / b
}
func main() {
fmt.Println(divide(10, 2)) // 5
fmt.Println(divide(10, 0)) // Panic!
}
Recover
Basic Recover
package main
import "fmt"
func safeDivide(a, b int) (result int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
result = 0
}
}()
if b == 0 {
panic("division by zero")
}
return a / b
}
func main() {
fmt.Println(safeDivide(10, 2)) // 5
fmt.Println(safeDivide(10, 0)) // 0 (recovered)
}
Recover with Error Handling
package main
import (
"fmt"
"log"
)
func processData(data []int) {
defer func() {
if r := recover(); r != nil {
log.Printf("Panic recovered: %v", r)
}
}()
for i := 0; i < 10; i++ {
fmt.Println(data[i]) // Panic if index out of bounds
}
}
func main() {
data := []int{1, 2, 3}
processData(data)
fmt.Println("Program continues")
}
Defer Patterns
Multiple Defers
package main
import "fmt"
func transaction() {
fmt.Println("Begin transaction")
defer func() {
fmt.Println("Rollback")
}()
defer func() {
fmt.Println("Commit")
}()
fmt.Println("Execute queries")
}
func main() {
transaction()
}
// Output:
// Begin transaction
// Execute queries
// Commit
// Rollback
Defer with Closures
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer func(n int) {
fmt.Println("Deferred:", n)
}(i)
}
}
// Output:
// Deferred: 2
// Deferred: 1
// Deferred: 0
Best Practices
โ Good: Use Defer for Cleanup
// DO: Use defer for resource cleanup
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
// Process file
return nil
}
โ Bad: Manual Cleanup
// DON'T: Forget cleanup in error paths
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
// Process file
file.Close() // Might not execute if error occurs
return nil
}
โ Good: Recover Only When Necessary
// DO: Use recover for exceptional cases
defer func() {
if r := recover(); r != nil {
log.Printf("Panic: %v", r)
}
}()
โ Bad: Overuse Panic/Recover
// DON'T: Use panic for normal error handling
if err != nil {
panic(err) // Use error returns instead
}
โ Good: Document Panic Behavior
// DO: Document when functions panic
// Divide panics if divisor is zero
func Divide(a, b int) int {
if b == 0 {
panic("division by zero")
}
return a / b
}
Summary
Defer, panic, and recover provide:
- Automatic cleanup with defer
- Exception-like behavior with panic/recover
- Guaranteed execution of deferred functions
- LIFO ordering for multiple defers
- Resource safety patterns
These features enable robust error handling and resource management in Go.
Comments