Go Closer provides utility functions for closing resources in Go.
To ensure a resource is always closed in Go, it is common practice to defer a
call to close the resource immediately after opening it successfully. Consider
an openResource
function with three return values: a resource, a callback
function to close the resource, and an error if the resource could not be
opened. A common practice is to write the following:
func f() error {
res, closeFn, err := openResource()
if err != nil {
return nil, err
}
defer closeFn()
return useResource(res)
}
However, this solution is less satisfying in the case that the closeFn
callback can itself return an error. In such cases, it is appropriate to use
closer.Close
or closer.CloseAndLog
to ensure the resource is closed but the
error is still surfaced in some way. An example use of closer.Close
:
func f() (rerr error) {
res, closeFn, err := openResource()
if err != nil {
return nil, err
}
defer closer.Close(&rerr, closeFn, "error closing resource")
return useResource(res)
}
The deferred closer.Close
ensures:
- That
closeFn
is called regardless of the outcome ofuseResource
-- whether it returns normally, with an error, or raises a panic. - If both
useResource
andcloseFn
return errors,f
returns the same error asuseResource
and the error returned bycloseFn
is logged. - If
closeFn
returns an error butuseResource
does not,f
returns the same error ascloseFn
.
There are cases in which that last property may not be desirable. That is, you
may not want to return the error produced by closeFn
even when useResource
has returned normally. Perhaps you agree
with the argument
that returning an error on close should only be done when writing to a resource,
not when reading. Or perhaps the outer function f
complies with an interface
that doesn't permit returning an error. In these cases, use closer.CloseAndLog
to unconditionally log any error returned by closeFn
:
func f() error {
res, closeFn, err := openResource()
if err != nil {
return nil, err
}
defer closer.CloseAndLog(closeFn, "error closing resource")
return useResource(res)
}
Sometimes a function intends to leave a resource open when it returns normally
but wishes to close it in the case it returns an error. For such cases, use
closer.CloseOnErr
or closer.CloseVoidOnErr
. An example:
func f() (val Value, rerr error) {
res, closeFn, err := openResource()
if err != nil {
return nil, err
}
defer closer.CloseOnErr(&rerr, closeFn, "error closing resource")
return wrapResource(res)
}