Skip to content
AgileLogics
6 min read

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 Counter contains only the value-receiver methods (Value, BrokenReset).
  • The method set of *Counter contains 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 BrokenReset above.
  • 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 nil pointer 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
}