In Go, deferred calls (defer) are used together with anonymous closures to guarantee cleanup or completion of working with resources. This pattern allows for grouping cleanup logic and handling errors properly, ensuring readable and reliable code.
Background:
Defer is borrowed from other languages and greatly simplifies the life of a Go developer. The combination of defer and closures has become a standard for ensuring the cleanup of files, connections, and any external resources in blocks where there may be many exits (return, panic).
Issue:
With complex resource cleanup logic (for example, files, connections, locks), it is necessary to ensure that even in the case of an error or exit from the function, cleanup will occur. Improper use can lead to leaks, incorrect order of cleanup, or meaningless errors.
Solution:
Use defer with an anonymous function (closure) to:
Code example:
package main import ( "fmt" "os" ) func WriteFileDemo(filename string) (err error) { f, err := os.Create(filename) if err != nil { return } defer func() { cerr := f.Close() if cerr != nil && err == nil { err = cerr } }() // Working logic with the file fmt.Fprintln(f, "Hello world") return // defer will execute even if there is a return here } func main() { if err := WriteFileDemo("test.txt"); err != nil { fmt.Println("Error:", err) } }
Key features:
When do variables used inside a deferred closure get captured — at the time of the defer declaration or at the actual call?
They are captured at the time of the defer declaration, but if the closure refers to variables by reference, their values at the time of executing the defer will be used. Sometimes this leads to unexpected results.
for i := 0; i < 3; i++ { defer func() { fmt.Println(i) }() // will print 2, 2, 2 }
Can values be passed to closures through parameters to avoid reference capture?
Yes, you can declare parameters for the anonymous function and pass the current values to them — then the values are “frozen” as copies.
for i := 0; i < 3; i++ { defer func(n int) { fmt.Println(n) }(i) // will print 2, 1, 0 }
What to do if a panic is detected in a deferred closure? How to handle it?
Inside the closure, use the recover() construct to prevent the panic from escaping and implement a soft recovery of functionality.
defer func() { if r := recover(); r != nil { log.Println("Recovered:", r) } }()
The code opens several files but forgets to place defer f.Close(). When an error occurs, control is returned, and some files remain unclosed, resulting in resource leaks.
Pros:
Cons:
A deferred closure is used for all production operations: carefully close the file stream and handle errors even if the file was not fully written.
Pros:
Cons: