Skip to content
Theory · Structs and methods · lesson 5 of 11

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:

GO
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:

  1. The first call pl.Add(5) gets a copy with score equal to 0 and returns 5.
  2. The second call gets a new copy of pl, where score is still 0, and returns 5 as 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.

GO
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:

  1. var pl Player creates a player with a score of 0.
  2. pl.AddPoints(5) follows the address of pl and adds 5 to the field score itself.
  3. pl.AddPoints(3) adds another 3 to the same field.
  4. fmt.Println(pl.score) prints 8.

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.

LiveA value receiver and a pointer receiverstep 1 of 9 · frames: 1

var pl Player creates a player with the score 0.

Watch pl in the main frame

Calling such a method is convenient:

  • you don't need to write (&pl).AddPoints(5): pl is a variable, and Go passes its address itself;
  • if a variable already holds a pointer, for example ptr := &pl, the call ptr.AddPoints(5) works too;
  • inside the method, p.score reaches the field through the pointer, like r.X on a struct;
  • through a pointer receiver the method also reads fields: p.score in an expression gives the current value of pl.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:

GO
Player{}.AddPoints(5) // ✗
TEXT
cannot call pointer method AddPoints on Player

The 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 call pl.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.

Sign in another way