Defer, panic, and recover are Go’s mechanisms for cleanup, error signaling, and exception-like handling. Understanding these is crucial for robust Go programs. For more context, see Go Installation Guide, Go Ecosystem Overview, Go Best Practices.
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