Imagine you need to wrap multiple objects which implements io.Closer, e.g. three clients to fetch and combine data from different endpoints.
type Parent struct { child1 Child1 child2 Child2 child3 Child3 } Parent closer Let’s see how we can create and destroy a parent object.
func NewParent() (*Parent, error) { child1, err := NewChild1() if err != nil { return nil, err } child2, err := NewChild1() if err != nil { // oops, child1 needs to be closed here child1.Close() return nil, err } child3, err := NewChild1() if err != nil { // oops again, both child1, and child2 needs to be closed here child1.Close() child2.Close() return nil, err } return &Parent{ child1: child1, child2: child2, child3: child3, }, nil } func (p *Parent) Close() error { var errs []error if err := p.child1.Close(); err != nil { errs = append(errs, err) } if err := p.child2.Close(); err != nil { errs = append(errs, err) } if err := p.child3.Close(); err != nil { errs = append(errs, err) } return multierr.Combine(errs...) } Note the boilerplate code of closing the children. Because the parent creates its children, it must be responsible for calling their Close method whenever needed. If there are any errors during the initialisation, the children already created have to be properly closed, and before the parent exits its scope, it has to close its children too.
...