Go is a language with automatic memory management: the garbage collector, without the need for a programmer, takes care of reclaiming the memory occupied by objects no longer used by the program. But all automation in general is limited by memory - we still need to take care of the rest of the resources used by the program ourselves.
, - , runtime.SetFinalizer. , , . Go - , , , , .
res1, err := NewResource1()
if err != nil {
return nil, err
}
res2, err := NewResource2(res1)
if err != nil {
res1.Close()
return nil, err
}
res3, err := NewResource3(res2)
if err != nil {
res2.Close()
res1.Close()
return nil, err
}
v, err := res3.DoSomething()
if err != nil {
res3.Close()
res2.Close()
res1.Close()
return nil, err
}
res3.Close()
res2.Close()
res1.Close()
return v, nil
. , C# Java using statement try-with-resources statement . Go , , defer statement. , :
res1, err := NewResource1()
if err != nil {
return nil, err
}
defer res1.Close()
res2, err := NewResource2(res1)
if err != nil {
return nil, err
}
defer res2.Close()
res3, err := NewResource3(res2)
if err != nil {
return nil, err
}
defer res3.Close()
return res3.DoSomething()
Close . Close - .
, . - defer . , , - , . , , . , , - , - - , (, main), , , , , .
, Wire. ( Wire) . . cleanup function, . .
Dedicated finalization
, c cleanup function, Wire, . , Close ( ) :
;
.
, , , . , Go :
res, cleanup, err := NewResource()
if err != nil {
return err
}
// cleanup, .
if err := res.DoSomething(); err != nil {
return err
}
, (, ) ( ) . , " ", , , "" "" , .
Composite finalization
defer (, ), :
func Finalize(finalizers ...func()) {
// .
for i := len(finalizers) - 1; i >= 0; i-- {
func() {
defer func() {
// , .
// multierror :
// 1) ;
// 2) .
recover()
}()
finalizers[i]()
}()
}
}
func NewResource3() (*Resource3, func(), error) {
var (
finalizers []func() //
successful bool //
)
defer func() {
// ,
// -
// .
if !successfull {
Finalize(finalizers...)
}
}()
res1, fin1, err := NewResource1()
if err != nil {
return nil, nil, err
}
finalizers = append(finalizers, fin1)
res2, fin2, err := NewResource2(res1)
if err != nil {
return nil, nil, err
}
finalizers = append(finalizers, fin2)
res3 := &Resource3{
resource2: res2,
}
fin3 := func() {
Finalize(finalizers...)
}
// .
//
// .
successful = true
return res3, fin3, nil
}
Finalize - .
new - error - defer , , , , .
KDone
I have posted the KDone library providing the above set of tools. She is part of the Kata project , which will be discussed next. We can assume that at the moment its API is stable and if it changes, it will be insignificant - nevertheless, the library is still fresh and I still use the zero major version in case of unforeseen changes.
A typical constructor using this library looks like this:
func NewResource(...) (res Resource, dtor kdone.Destructor, err error) {
defer kerror.Catch(&err) //
// KError.
// .
reaper := kdone.NewReaper() // reaper.
defer reaper.MustFinalize() //
// .
// ... reaper.MustAssume(dtor) ... // reaper
//
// .
return res, reaper.MustRelease(), nil // reaper
//
// .
}
What do you think? The concept is simple enough, but maybe I missed something in my reasoning? Or do you have suggestions for improvements? I would be glad to have discussions in the comments.