4

I read The Go Programming Language Book recently, the good resource for learning golang programming language. There is a paragraph in 6.2 section about copy instance of type T when it is pointer receiver or not in methods, that I can't understand it. Is there any that explain this paragraph with a meaningful example?

6.2 Methods with a Pointer Receiver

If all the methods of a named type T have a receiver type of T itself (not *T ), it is safe to copy instances of that type; calling any of its methods necessarily makes a copy. For example, time.Duration values are liberally copied, including as arguments to functions. But if any method has a pointer receiver, you should avoid copying instances of T because doing so may violate internal invariants. For example, copying an instance of bytes.Buffer would cause the original and the copy to alias ( §2.3.2 ) the same underlying array of bytes. Subsequent method calls would have unpredictable effects.

(The Go Programming Language Alan A. A. Donovan · Brian W. Kernighan)

arastu
  • 432
  • 5
  • 5
  • 1
    Basically what this is saying is that if you have a type that has no pointer receiver methods at all (so no code like `func (t *T)`) you can assume that it can be copied as no methods will ever modify the data in the type. – GarMan Jan 05 '17 at 09:01
  • This book is great because they pay attention to not only primitive features. But sometimes explanation or accent maybe is tricky. Maybe this issue of language - The same author Brian was very comprehensive in C programming language. – Vlad Aug 16 '22 at 05:23

1 Answers1

5

When calling a method, the value the method is called on is first copied, and that copy is passed / used as the receiver.

If a type only has methods with value receivers, that means no matter what the methods do inside, and no matter what methods you (or anyone else) call, the methods won't be able to change the original value, because –as noted above– only a copy is passed and the method could only modify the copy – and not he original.

So this means if you copy the value, you don't have to worry, neither the methods called on the original nor on the copy can't / won't modify the value(s).

Not, when the type has methods with pointer receivers. If a method has a pointer receiver, the method can change / modify the pointed value, which is not a copy, it's the original value (only the pointer is a copy but it points to the original value).

Let's see an example. We create an int wrapper type, which has 2 fields: an int and an *int. We intend to store the same number in both fields, but one is a pointer (and we store the int in the pointed value):

type Wrapper struct {
    v int
    p *int
}

To make sure both values (v and *p) are the same, we provide a Set() method, which sets both:

func (w *Wrapper) Set(v int) {
    w.v = v
    *w.p = v
}

Wrapper.Set() has a pointer receiver (*Wrapper) as it has to modify the value (which is of type Wrapper). No matter what number we pass to Set(), we can be sure that once Set() returns, both v and *p will be the same, and equal to the number passed to Set().

Now if we have a value of Wrapper:

a := Wrapper{v: 0, p: new(int)}

We can call the Set() method on it:

a.Set(1)

The compiler will automatically take the address of a to use as the receiver, so the above code means (&a).Set(1).

We'd expect that any value of type Wrapper has the same number stored in Wrapper.v and *Wrapper.pv, if only the Set() method is used to change the fields' values.

Now let's see the problem if we make a copy of a:

a := Wrapper{v: 0, p: new(int)}
b := a
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

a.Set(1)
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

Output (try it on the Go Playground):

a.v=0, a.p=0;  b.v=0, b.p=0
a.v=1, a.p=1;  b.v=0, b.p=1

We made a copy of a (stored it in b), and printed the values. So far so good. Then we called a.Set(1), after which a is still good, but internal state of b became invalid: b.v does not equal to *b.p anymore. The explanation is quite clear: when we made a copy of a (which is a struct type), that copies the values of its fields (including the pointer p), and the pointer in b will point to the same value as the pointer in a. Hence modifying the pointed value will affect both copies of Wrapper, but we have 2 distinct v fields (they are non-pointers).

If you have methods with pointer receivers, you should work with pointer values.

Note that if you would copy a value of *Wrapper, everything would still be cool:

a := &Wrapper{v: 0, p: new(int)}
b := a
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

a.Set(1)
fmt.Printf("a.v=%d, a.p=%d;  b.v=%d, b.p=%d\n", a.v, *a.p, b.v, *b.p)

Output (try it on the Go Playground):

a.v=0, a.p=0;  b.v=0, b.p=0
a.v=1, a.p=1;  b.v=1, b.p=1

In this case a is a pointer, it's of type *Wrapper. We made a copy of it (stored it in b), called a.Set(), and both the internal state of a and b remained valid. Here we only have one Wrapper value, a only holds a pointer to it (its address). When we copy a, we only make a copy of the pointer value, and not the struct value (of type Wrapper).

icza
  • 389,944
  • 63
  • 907
  • 827
  • It took 1 hour to learn the same pointer/value semantics in C++. But here I'm lost... Could you please explain you second paragraph: "...only a copy is passed". Copy of what and when? Say, a.f() will create copy of a during invocation if f declared as func (a A) f { } - that is receiver parameter is value? Usually in other languages like C++ calling method performs actions on object for what it is called - it doesn't copy associated object. I totally understand the situation with shared memory in a and b via p *int. I do not understand how method call can create copy of associated object. – Vlad Aug 16 '22 at 05:18
  • @Vlad The receiver value is what's copied. If the receiver is a pointer, then a copy of the pointer is used. If the receiver is a non-pointer, then the non-pointer value is copied. In Go there are no objects. If the receiver is a struct, a copy of the struct is made with identical field values, whatever they may be (pointers or non-pointers). But there are types in go that "act like references", like pointers, and a copy of them may point to the same underlying data. – icza Aug 16 '22 at 05:24
  • @Vlad For some examples, see [What do "value semantics’" and "pointer semantics" mean in Go?](https://stackoverflow.com/questions/51264339/what-do-value-semantics-and-pointer-semantics-mean-in-go/51265000#51265000); [Are slices passed by value?](https://stackoverflow.com/questions/39993688/are-slices-passed-by-value/39993797#39993797) and [why slice values can sometimes go stale but never map values?](https://stackoverflow.com/questions/55520624/why-slice-values-can-sometimes-go-stale-but-never-map-values/55521138#55521138) – icza Aug 16 '22 at 05:25
  • 1
    Thank you very much! I think I'm begging to understand. And further reading is valuable. I don't know why authors of this book do not emphasize on such importand points. C/C++ programmers know only that copy or assignment are performed during =; passing as argument, returning. It's paradigm shift that method invocation can copy associated variable. Thank you! – Vlad Aug 16 '22 at 05:33
  • In link you explains copy of arguments of functions - this is the same behavior as in C or C++. I suspect that even a is copied in expression a.f(). Say: type T struct { v int } func (t T) f() int {return t} Is t also considered as variable that will be copied during call to f()? – Vlad Aug 16 '22 at 05:58
  • Sorry - typo: func (t T) f() int {return 42} If Go designers consider methods as syntax sugar and function above as regular package level function func f (t T) int {return 42} this makes sense - then t will be copied – Vlad Aug 16 '22 at 06:04
  • @Vlad The receiver and all regular parameters are copied when a function is called. There are no reference types in Go in the C++ sense. Parameters (including named result types) act as local variables in the function. – icza Aug 16 '22 at 06:26
  • Thank you. You are right - I traced this in debug: Invoking method(like func (t T) f() {}) on receiver creates copy of receiver - parameter t. Go consider struct used to invoke method as other method's parameters and copy them. And this is surprise for C++ programmer - when they call method on object they expect to change this object's state. I think authors should emphasize the difference with other languages. To change struct used for method invocation we need to use func (t *T) f() {}. – Vlad Aug 17 '22 at 01:45