DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 967,611 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
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)

πŸ‘‹ Welcome the newest DEV members

Head over to our Welcome Thread and say hello!