Implementing Context Managers in Go: Emulating Python's with Using defer and Anonymous Functions
This article explores how to replicate Python's context manager behavior in Go by examining defer's delayed execution, demonstrating its limitations in loops, and presenting three solutions—anonymous functions, a WithClose helper, and a withLock-inspired pattern—to ensure timely resource release.
Go developers often use defer to release resources such as files or database connections, but defer is executed only when the surrounding function returns, which can cause delayed cleanup when used inside a loop.
In Python the with statement provides a built‑in context manager that guarantees immediate release after the block finishes. The article first shows the Python idiom:
with open('foo.txt', 'r') as f:
print(f.readlines())It then presents a straightforward Go version using defer :
func ReadFile(paths []string) error {
for _, path := range paths {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
content, err := io.ReadAll(file)
if err != nil {
return err
}
fmt.Printf("%s content: %s\n", file.Name(), content)
}
return nil
}Running this code shows that the defer calls are postponed until the function exits, so all files remain open during the loop.
To make the cleanup happen after each iteration, the article introduces three techniques.
1. Wrap the body of the loop in an immediately‑invoked anonymous function and place the defer inside it:
func ReadFile(paths []string) error {
for _, path := range paths {
file, err := os.Open(path)
if err != nil {
return err
}
defer func() {
file.Close()
fmt.Printf("close %s\n", file.Name())
}()
// process file
}
return nil
}2. Define a helper WithClose that accepts an io.Closer and a function, deferring the close inside the helper:
func WithClose(closer io.Closer, fn func()) {
defer func() {
closer.Close()
fmt.Printf("close %s\n", closer.(*os.File).Name())
}()
fn()
}Usage:
func ReadFile(paths []string) error {
for _, path := range paths {
file, err := os.Open(path)
if err != nil {
return err
}
WithClose(file, func() {
content, err := io.ReadAll(file)
if err != nil {
return
}
fmt.Printf("%s content: %s\n", file.Name(), content)
})
}
return nil
}3. Mimic the Python with pattern with a generic withLock function that locks a sync.Locker , runs a callback, and unlocks afterwards. The same idea can be applied to file closing.
All three approaches produce the expected output, releasing each file immediately after it is processed, and they also correctly propagate errors when a file cannot be opened.
The article concludes that, while defer is convenient, it should be avoided inside loops for resource‑intensive operations, and that anonymous functions, a WithClose helper, or a withLock -style wrapper provide clean and timely resource management in Go.
Go Programming World
Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.