3

According the following function declarations from sync package:

Add -------> func (wg *WaitGroup) Add(delta int)

Done ------> func (wg *WaitGroup) Done()

Wait ------> func (wg *WaitGroup) Wait()

I understand that all 3 of them are called by a pointer to a WaitGroup, right?

If this is correct, I don't understand in the next pice of code, why Done function is called using a pointer variable, but Add and Wait functions are called using a variable (not a pointer):


package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {

    defer wg.Done()

    fmt.Printf("Worker %d starting\n", id)

    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {

    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
}

Thanks a lot for your help.

Kevin Flynn
  • 111
  • 5
  • 2
    See [spec on calls](https://golang.org/ref/spec#Calls): *"A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. **If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()**"* – mkopriva Mar 23 '21 at 15:58
  • 4
    They are all called with a pointer receiver. https://golang.org/ref/spec#Selectors. See also https://stackoverflow.com/questions/30786206/are-pointers-dereferenced-by-default-inside-of-methods/30786320 and https://stackoverflow.com/questions/33587227/method-sets-pointer-vs-value-receiver, and https://stackoverflow.com/questions/19433050/go-methods-sets-calling-method-for-pointer-type-t-with-receiver-t, etc... – JimB Mar 23 '21 at 15:59
  • 5
    This is explained in the tour https://tour.golang.org/methods/6 – Hymns For Disco Mar 23 '21 at 16:02
  • Thanks a lot: mkopriva, JimB, Hymns For Disco. I will try to study and understand the explanation you all wrote for me. Many thanks all. – Kevin Flynn Mar 23 '21 at 16:52

1 Answers1

3

Done, Add and Wait are called on pointer. All functions refer to a pointer receiver *WaitGroup. The fact that you declare variable as value of WaitGroup doesn't mean much as all those methods will all access and modify the variable. The only problem happens when you want to pass your variable to worker - if you try to pass it as value you will make a copy and then Done will be referring to different pointer than Add and Wait - that's why you pass it's address with &.

I think here is best explanation I so far seen on the topic: https://github.com/golang/go/wiki/MethodSets#variables

In general, when you have a variable of a type, you can pretty much call whatever you want on it. When you combine the two rules above together, the following is valid:

type List []int

func (l List) Len() int        { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }

func main() {
    // A bare value
    var lst List
    lst.Append(1)
    fmt.Printf("%v (len: %d)\n", lst, lst.Len())

    // A pointer value
    plst := new(List)
    plst.Append(2)
    fmt.Printf("%v (len: %d)\n", plst, plst.Len())
}

Note that both pointer and value methods can both be called on both pointer and non-pointer values. To understand why, let's examine the method sets of both types, directly from the spec:

List
- Len() int

*List
- Len() int
- Append(int) 

Notice that the method set for List does not actually contain Append(int) even though you can see from the above program that you can call the method without a problem. This is a result of the second spec section above. It implicitly translates the first line below into the second:

lst.Append(1)
(&lst).Append(1)

Now that the value before the dot is a *List, its method set includes Append, and the call is legal.

To make it easier to remember these rules, it may be helpful to simply consider the pointer- and value-receiver methods separately from the method set. It is legal to call a pointer-valued method on anything that is already a pointer or whose address can be taken (as is the case in the above example). It is legal to call a value method on anything which is a value or whose value can be dereferenced (as is the case with any pointer; this case is specified explicitly in the spec).

Kamil Dziedzic
  • 4,721
  • 2
  • 31
  • 47