A value receiver makes a method operate on a copy of its receiver. If a method changes a field on that copy, the original value does not change. This is easy to miss when a method name suggests mutation. section 6.1
type customer struct {
balance float64
}
func (c customer) add(v float64) { #1
c.balance += v
}
func main() {
c := customer{balance: 100.}
c.add(50.)
fmt.Printf("balance: %.2f\n", c.balance) #2
}
Calling `add(50)` leaves the original balance at `100.00`: `c.balance += v` changes only the copied receiver. section 6.1
Use a pointer receiver when the method must change the original value. In the pointer-receiver version of `add`, the same call changes the balance to `150.00`. Go still copies the receiver argument, but that argument is a pointer rather than the whole struct. section 6.1
A copied struct can still reach shared data
Suppose a struct holds `data *data`, and `add` has a value receiver. The struct is copied, but its pointer still identifies the same data. Changing `c.data.balance` therefore changes the balance visible through the original struct. Do not treat a value receiver as a guarantee that a method cannot change anything shared. section 6.1
Receiver choice matters for deferred methods, too. A deferred call evaluates its receiver immediately. With a value receiver, changing the original struct before the deferred method runs does not change the saved copy; with a pointer receiver, a later change to the pointed-to struct is visible. section 6.6.2
func main() {
s := Struct{id: "foo"}
defer s.print() #1
s.id = "bar" #2
}
type Struct struct {
id string
}
func (s Struct) print() {
fmt.Println(s.id) #3
}
The deferred value-receiver method prints `foo`, not `bar`, because its receiver was copied before `s.id` changed. section 6.6.2
A more serious copying mistake is a value-receiver method on a struct containing `sync.Mutex`. Each call copies the mutex, so callers do not lock the same critical section; concurrent increments can race. Make the receiver a pointer, or store a pointer to the mutex and initialize it. `go vet` can flag a method that passes a lock by value. section 9.14
type Counter struct {
mu sync.Mutex
counters map[string]int
}
func NewCounter() Counter { #1
return Counter{counters: map[string]int{}}
}
func (c Counter) Increment(name string) {
c.mu.Lock() #2
defer c.mu.Unlock()
c.counters[name]++
}
This `Increment` method copies `Counter`—including its mutex—on every call, despite locking before updating the map. section 9.14
Choose by behavior and type, not by a blanket performance rule. Use a pointer receiver when mutation or a non-copyable field requires one; consider one for a large receiver to avoid an extensive copy. Benchmark when size makes the performance choice uncertain. section 6.1 section 6.7


