Pointer Receivers & Method Sets
Prerequisites: Modules & packages
In Level 1 your methods used value receivers: the method gets a copy of
the struct, so changes inside it are lost. A pointer receiver (*T) gets
the address of the original, so the method can modify it.
Syntax
func (c Counter) Value() int { return c.n } // value receiver: works on a copy
func (c *Counter) Increment() { c.n++ } // pointer receiver: modifies the original
Example
package main
import "fmt"
type Counter struct {
n int
}
func (c Counter) Value() int {
return c.n
}
func (c *Counter) Increment() {
c.n++
}
// BrokenReset has a value receiver, so it only resets its own copy.
func (c Counter) BrokenReset() {
c.n = 0
}
func main() {
var c Counter
c.Increment() // Go automatically takes &c for you
c.Increment()
fmt.Println(c.Value())
c.BrokenReset()
fmt.Println(c.Value())
p := &c
p.Increment()
fmt.Println(p.Value()) // Go automatically dereferences p for you
}
Expected output
2
2
3
Method sets
The method set of a type decides which interfaces it satisfies (interfaces are the next lesson):
- The method set of
Countercontains only the value-receiver methods (Value,BrokenReset). - The method set of
*Countercontains both value- and pointer-receiver methods.
The automatic &c shortcut only works when calling a method on an
addressable variable; it does not change the method set. So only *Counter
has Increment in its method set:
type Incrementer interface {
Increment()
}
var a Incrementer = &Counter{} // OK
// var b Incrementer = Counter{} // compile error: method Increment has pointer receiver
When to use a pointer receiver: when the method must modify the receiver,
when the struct is large enough that copying it is wasteful, or when the type
contains something that must not be copied (such as a sync.Mutex). If any
method of a type needs a pointer receiver, the usual convention is to give all
of its methods pointer receivers for consistency.
Common mistakes
- Writing a "setter" with a value receiver — it compiles, but the change
disappears when the method returns, exactly like
BrokenResetabove. - Calling a pointer method on a value that isn't addressable, such as a map
element:
m["a"].Increment()does not compile. Store pointers in the map (map[string]*Counter) or copy the value out, modify it, and store it back. - Calling a pointer-receiver method through a
nilpointer and then reading a field — that panics with a nil pointer dereference.
Practice
Create an Account type with a balance int field, a pointer-receiver
method Deposit(amount int) and a value-receiver method Balance() int.
Deposit 50 twice and print the balance.
Show solution
package main
import "fmt"
type Account struct {
balance int
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
func (a Account) Balance() int {
return a.balance
}
func main() {
var acc Account
acc.Deposit(50)
acc.Deposit(50)
fmt.Println(acc.Balance()) // 100
}