Embedding Types to Reuse Code with Less Noise

2020-04-30 • 2 minutes to read

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()
}

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
}

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()
}

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()
}

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.

software engineeringphp2gogolangcompositioninheritancepolymorphism

Semaphored Wait Group