Suppose you have to initialize a chain of resources in order to do something, typically with one initialization depending on the next. For example, you need to launch a browser, to open a browser window, to open a tab, to navigate that tab to a web site. At the end of the operation, you want to close or tear down every resource you have initialized.
Let's look at this naive code:
func main() {
window, err := NewWindow()
if err != nil {
panic(err)
}
defer window.Close()
tab, err := NewTab(window)
if err != nil {
panic(err)
}
defer tab.Close()
NavigateToSite(tab)
}
(Of course, this code is pretty simple, so one might ask why ever refactor it, so keep in mind it's example's sake, and the actual chain of initializations might be longer and more convoluted.)
Suppose then I want to factor out the initialization, noticing that the actual logic in my code doesn't need the window
at all. What would be an idiomatic way to do it? So far I can think of:
func main() {
rs, err := NewMyResource()
if err != nil {
panic(err)
}
defer rs.Close()
NavigateToSite(rs.Tab)
}
struct MyResource {
Window *window;
Tab *tab;
}
func NewMyResource() (*MyResource, error) {
rs := &MyResource{}
window, err := CreateWindow()
if err != nil {
rs.Close()
return nil, err
}
rs.Window = window
tab, err := CreateTab()
if err != nil {
rs.Close()
return nil, err
}
rs.Tab := tab
return rs, nil
}
func (rs MyResource) Close() {
if rs.Window != nil {
rs.Window.Close()
}
if rs.Tab != nil {
rs.Tab.Close()
}
}