What is the proper way to manage resource ownership in golang? Suppose I have the following:
db, err := sql.Open("mysql", "role@/test_db")
am := NewResourceManager(db)
am.DoWork()
db.Close()
Is it typical to always have the calling function maintain ownership and responsibility for closing resources? This feels a little weird to me because after closing, am
still retains a reference and could try to use db
if I or someone else is not careful later on (I guess this is a case for defer; however, if I want to pass the ResourceManager am
back from this block, how would I even defer the closing of the file properly? I actually want it to stay open when this block finishes execution). I find that in other languages I often want to allow the instance to manage the resource and then clean it up when it's destructor is called, like this toy python example:
class Writer():
def __init__(self, filename):
self.f = open(filename, 'w+')
def __del__(self):
self.f.close()
def write(value):
self.f.write(value)
Unfortunately, there are no destructors in golang. I'm not sure how I would do this in go other than something like this:
type ResourceManager interface {
DoWork()
// Close() ?
}
type resourceManager struct {
db *sql.DB
}
func NewResourceManager(db *sql.DB) ResourceManager {
return &resourceManager{db}
}
db, err := sql.Open("mysql", "role@/test_db")
am := NewResourceManager(db)
am.DoWork()
am.Close() // using method shortening
But this seems less transparent, and I'm not sure how to communicate that the ResourceManager also needs to be Close()'d now. I'm finding this a frequent stumbling block, i.e. I also want to have a resource manager that holds a gRPC client connection, and if these types of resources aren't managed by resource managing objects, it seems like my main function is going to be cluttered with a lot of resource management, i.e. opening and closing. For instance, I could imagine a case where I wouldn't want main
to know anything about the object and it's resources:
...
func NewResourceManager() ResourceManager {
db, err := sql.Open("mysql", "role@/test_db")
return &resourceManager{db}
}
...
// main elsewhere
am := NewResourceManager()
am.DoWork()