DEV Community

Paweł Zaremba
Paweł Zaremba

Posted on • Originally published at tegh.net

Embedding Types in Go Reduces Noise

My previous post was about one of the concurrency patterns that can be achieved with go.
I immediately got some feedback on it, and this is where I will address one of them.

You have seen the code reimplementing sync.WaitGroup to have a semaphore in it.

type WaitGroup interface {
    Add(delta int)
    Done()
    Wait()
}

type SemaphoredWaitGroup struct {
    sem chan bool
    wg  sync.WaitGroup
}

func (s *SemaphoredWaitGroup) Add(delta int) {
    s.wg.Add(delta)
    s.sem <- true
}

func (s *SemaphoredWaitGroup) Done() {
    <-s.sem
    s.wg.Done()
}

func (s *SemaphoredWaitGroup) Wait() {
    s.wg.Wait()
}
Enter fullscreen mode Exit fullscreen mode

It has been brought to my attention (Thanks, Wojtek!), that there is a "cleaner" way to do it in Go.

Enter: Type Embedding

Those coming from other languages (e.g.: PHP), you might notice that there is no such thing as an extends keyword.
There is no subclassing in the popular way. What we do have is type embedding.

To achieve that we change SemaphoredWaitGroup:

type SemaphoredWaitGroup struct {
    sem chan bool
-   wg  sync.WaitGroup
+   sync.WaitGroup
}
Enter fullscreen mode Exit fullscreen mode

All methods from the "inner" (embedded) struct are now a part of the "outer" (embedding) type.
It is even possible to access them directly by calling them as if they were actually defined locally. E.g:

func (s *SemaphoredWaitGroup) newMethod() {
    s.Wait()
}
Enter fullscreen mode Exit fullscreen mode

There is a catch though. when we re-define the methods from the inner part (here we have: Add(delta int) and Done()), we need to change the internal calls

func (s *SemaphoredWaitGroup) Add(delta int) {
-   s.Add(delta)
+   s.WaitGroup.Add(delta)
    s.sem <- true
}

func (s *SemaphoredWaitGroup) Done() {
    <-s.sem
-   s.Done()
+   s.WaitGroup.Done()
}
Enter fullscreen mode Exit fullscreen mode

because the s.Add(delta) and s.Done() would be recursive calls, and would result in a "stack overflow" error in this particular instance.

Also, we don't need to have the Wait() method pass calls through to the underlying sync.WaitGroup struct.

A complete example is available on Go Playground.

This was originally posted at: https://www.tegh.net/9

Top comments (0)