Pointer receivers
In the previous lesson a method was an action a form knows how to do. But which form does the method work on—your original or a photocopy? The answer decides whether you see its changes. The method AddPoints should add points to a player. The program compiles, but the score stays at zero after the call:
package main
import "fmt"
type Player struct{ score int }
func (p Player) AddPoints(k int) { p.score += k }
func main() {
var pl Player
pl.AddPoints(5)
fmt.Println(pl.score) // 0
}The cause is the method's receiver. A receiver comes in two kinds, and we'll go through each now.
Two kinds of receiver
A receiver is a parameter like any other, and Go copies every argument, even a whole struct. The kind of receiver decides what gets copied: the whole form or a slip with its address.
Value receiver
A receiver of the form (p Player) is called a value receiver. It is written as the name p and the type Player, with no star.
On every call the method gets a new photocopy of the form pl, and the statement p.score += k changes only that copy. There is no compile error: a method may change its receiver, but the change disappears with the copy.
Inside the method the copy really changes. The method func (p Player) Add(k int) int { p.score += k; return p.score } returns the increased score. A semicolon separates two statements on one line. Let's look at two calls in a row:
- The first call
pl.Add(5)gets a copy withscoreequal to0and returns5. - The second call gets a new copy of
pl, wherescoreis still0, and returns5as well.
This receiver suits methods that only read fields. The trap is that a write to a field compiles but is silently lost.
Pointer receiver
For a method to change the variable itself, its receiver is declared as a pointer: (p *Player). This is a pointer receiver. The star before Player is the whole difference in how it is written. Recall the slip of paper with a house's address from the pointers theme: such a method gets not a photocopy of the form but the address of the original.
package main
import "fmt"
type Player struct{ score int }
func (p *Player) AddPoints(k int) { p.score += k }
func main() {
var pl Player
pl.AddPoints(5)
pl.AddPoints(3)
fmt.Println(pl.score) // 8
}Let's walk through the example line by line:
var pl Playercreates a player with a score of0.pl.AddPoints(5)follows the address ofpland adds5to the fieldscoreitself.pl.AddPoints(3)adds another3to the same field.fmt.Println(pl.score)prints8.
The copy rule still holds: the method gets a copy of the address slip, and the copy leads to the same form.
Let's compare both receivers in one program. Press "Next step" and watch pl in the main frame: the AddCopy method changes only its own copy.
1–4package main · import "fmt"5type Player struct{ score int }6 7func (p Player) AddCopy(k int) {8 p.score += k9}10 11func (p *Player) AddPoints(k int) {12 p.score += k13}14 15func main() {16 var pl Player17 pl.AddCopy(5)18 fmt.Println(pl.score)19 pl.AddPoints(5)20 pl.AddPoints(3)21 fmt.Println(pl.score)22}
var pl Player creates a player with the score 0.
Calling such a method is convenient:
- you don't need to write
(&pl).AddPoints(5):plis a variable, and Go passes its address itself; - if a variable already holds a pointer, for example
ptr := &pl, the callptr.AddPoints(5)works too; - inside the method,
p.scorereaches the field through the pointer, liker.Xon a struct; - through a pointer receiver the method also reads fields:
p.scorein an expression gives the current value ofpl.score.
This receiver has one limit: Go must be able to take an address. The common mistake section shows what happens when it cannot.
Which receiver to choose
Interviewers often ask when a pointer receiver is needed. The usual answer:
- The method changes fields—a pointer receiver.
- The method only reads the fields of a small struct—a value receiver: it cannot change the original anyway.
- The struct is large—a pointer receiver even for reading: copying an address is faster than copying every field.
- If at least one method of a type needs a pointer, the Go guidelines advise giving a pointer to all its methods.
The last rule is advice, not a requirement. The language lets you mix receivers, and such code works. But when every method of a type works on the original, a reader does not have to check each receiver.
Common mistake
A method with a pointer receiver is called on a literal, not on a variable:
Player{}.AddPoints(5) // ✗cannot call pointer method AddPoints on PlayerThe program does not compile: Player{} is not a variable, so Go cannot take its address for the method by itself. Store the value in a variable first—pl := Player{}—and call pl.AddPoints(5).
Key points
- A value receiver
(p Player)gets a copy: changes to fields are lost after the call. - A pointer receiver
(p *Player)changes the variable itself; Go takes its address for the callpl.AddPoints(5). - A method that changes fields is declared with a pointer receiver.
- A method with a pointer receiver cannot be called on a literal such as
Player{}.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.