It is true Files are closed when garbage collected, but... as mentioned in "Mystery of finalizers in Go" from Alexander Morozov -- LK4D4math:
In Go we have both GC and pro-users :)
So, in my opinion explicit call of Close is always better than magic finalizer.
Alexander adds:
The problem with finalizers is that you have no control over them, and, more than that, you’re not expecting them.
Look at this code:
func getFd(path string) (int, error) {
f, err := os.Open(path)
if err != nil {
return -1, err
}
return f.Fd(), nil
}
It’s pretty common operation to get file descriptor from path when you’re writing some stuff for linux.
But that code is unreliable, because when you’re return from getFd(), f loses its last reference and so your file is doomed to be closed sooner or later (when next GC cycle will come).
Here, the problem is not that file will be closed, but that it is not documented and not expected at all.
There was a proposal to extend the finalizer and detecting leaks (like file descriptor leaks)
But... Russ Cox quashed that down convincingly:
Anyone interested in this topic should read Hans Boehm's paper "Destructors, Finalizers, and Synchronization".
It greatly influenced our decision to limit the scope of finalizers in Go as much as possible.
They are a necessary evil for allowing reclamation of non-(heap memory) resources at the same time as associated heap memory, but they are inherently much more limited in their capabilities than most people initially believe.
We will not be expanding the scope of finalizers, either in implementation or in the standard library, or in the x repos, nor will we encourage others to expand that scope.
If you want to track manually-managed objects, you'd be far better off using runtime/pprof.NewProfile.
For example, inside Google's source tree we have a Google-wide "file" abstraction, and the Go wrapper package for this declares a global:
var profiles = pprof.NewProfile("file")
The end of the function that creates a new File says:
profiles.Add(f, 2)
return f
and then f.Close does
profiles.Remove(f)
Then we can get a profile of all in-use files, "leaked" or otherwise, from /debug/pprof/file or from pprof.Lookup("file").WriteTo(w, 0).
And that profile includes stack traces.