4

I have seen a few different examples of sync.WaitGroup

Example 1

var wg sync.WaitGroup

wg.Add(1)
go doStuff(&wg)
wg.Wait()

Example 2

wg := new(sync.WaitGroup)

wg.Add(1)
go doStuff(wg)
wg.Wait()

the difference is really in the way sync.WaitGroup is initialized var vs new

if using the var option it has to be passed as a pointer &wg to the goroutine but if I use the new option I can send it as wg

What is the difference between the two examples? Which of these 2 above is correct? Is one preferred over the other in certain situations?

I am writing a program which creates multiple sync.WaitGroups so does it matter if new or var is used?

icza
  • 389,944
  • 63
  • 907
  • 827
rrag
  • 587
  • 6
  • 11
  • 2
    Possible duplicate of [Is there a difference between new() and "regular" allocation?](https://stackoverflow.com/questions/13244947/is-there-a-difference-between-new-and-regular-allocation) – Jonathan Hall Dec 15 '18 at 11:07

1 Answers1

8

Both of your examples work properly. Also note that instead of new(), you could also use a composite literal and take its address like this:

var wg = &sync.WaitGroup{}

Methods of sync.WaitGroup have pointer receiver, so whenever you call its methods, the address of the WaitGroup struct value is needed. This is not a problem, as when wg is a non-pointer, the wg.Add(1) and wg.Done() calls are shorthand for (&wg).Add(1) and (&wg).Done(), so the compiler automatically "rewrites" those calls to take the address of wg first, and use that address as the receiver of the methods.

However, I still think that if a value is only useful as a pointer (sync.WaitGroup is a shining example), you should declare it and work with it as a pointer in the first place, so that leaves less room for errors.

For example, if you use a non-pointer and you declare the function to expect a non-pointer, and you pass it as a non-pointer, you will get no compile-time error, yet it will misbehave (sync.WaitGroup should not be copied).

Although today's linter would give you a warning message, still, I believe it's best to work with pointers all the time.

Another reason to work with pointers: if a function would return a sync.WaitGroup, or if you have a map that stores sync.WaitGroup as values, you would not be able to call methods on the result, because return values of functions and map index operations are not addressable. If the function would return a pointer value, or if you would store pointers in the map in the first place, you could still call the methods without having to store them in a local variable. For details, see How can I store reference to the result of an operation in Go?

For example:

func getWg() sync.WaitGroup { return sync.WaitGroup{} }

getWg().Wait() // Compile-time error!

m := map[int]sync.WaitGroup{
    1: sync.WaitGroup{},
}

m[1].Wait() // Again: compile-time error

But these work:

func getWg() *sync.WaitGroup { return &sync.WaitGroup{} }

getWg().Wait() // Works, you can call methods on the return value

m := map[int]*sync.WaitGroup{
    1: &sync.WaitGroup{},
}

m[1].Wait() // Also works

Read more about this here: Why should constructor of Go return address?

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks, it helps to understand that both are correct. Since the `new` / composite literal technique creates a pointer. are you suggesting to use Example 2 style since `var` is a non-pointer? – rrag Dec 15 '18 at 12:12
  • @rrag Yes, I'm suggesting to use example 2, and it's more common to use `&sync.WaitGroup{}` than `new(sync.WaitGroup)`, although they do the same. – icza Dec 15 '18 at 12:33