Pointer Receivers
A receiver is a parameter, and Go passes parameters by value — the method gets a copy. That's fine for reading, but it means a value receiver can never change the original:
package main
import "fmt"
type Account struct {
balance int
}
// Broken: deposits into a copy that vanishes when the method returns.
func (a Account) DepositBroken(amount int) {
a.balance += amount
}
// Works: a *Account points at the caller's struct.
func (a *Account) Deposit(amount int) {
a.balance += amount
}
func main() {
acct := Account{}
acct.DepositBroken(50)
fmt.Println(acct.balance) // 0 — the copy got the money
acct.Deposit(50)
fmt.Println(acct.balance) // 50
}
With the receiver type *Account, the method receives the address of the
caller's struct, so a.balance += amount mutates the real thing.
When to use which
Reach for a pointer receiver when:
- the method must mutate the receiver — the whole point above;
- the struct is large, and copying it on every call would be wasteful;
- the type contains a field that must not be copied (like a
sync.Mutex).
A value receiver suits small, immutable-feeling types: a Point, a
Celsius, a time.Time. The copy is cheap and the method visibly can't
change its receiver — that's information for the reader.
The consistency rule
If any method of a type needs a pointer receiver, give all of its
methods pointer receivers. A type whose methods are half value, half pointer
is confusing to use (its method set differs between T and *T, which
matters for interfaces later). One deliberate exception you'll meet in this
chapter's exercise: a read-only accessor like Value() is often left as a
value receiver to signal "this cannot mutate" — but when in doubt, match the
rest of the type.
Go meets you halfway at the call site
You don't write (&acct).Deposit(50) — Go inserts the & for you when you
call a pointer method on an addressable value. It works the other way too:
with a pointer p := &acct, calling a value-receiver method as p.Distance()
implicitly dereferences. So call sites look identical either way; the receiver
type is a fact about the method's declaration, not about how you invoke it.
One caveat: the automatic & needs something addressable. You can't call a
pointer method directly on a map element or on a temporary like
Account{}.Deposit(50) — assign to a variable first.
Try it
The exercise builds a Counter type: two mutating methods that must use
pointer receivers (Inc, Add) and one reader (Value) that doesn't. If
your counter stays at zero no matter how often you increment, you've just
reproduced the copy bug from the top of this lesson — check your receivers.
Practice
1. A Mutating Counter
IntermediateImplement a small counter type with two mutating methods and one reader:
type Counter struct{ n int } func (c *Counter) Inc() // add 1 to the count func (c *Counter) Add(delta int) // add delta to the count (delta may be negative) func (c Counter) Value() int // return the current countBehavior to satisfy:
- A zero-value
Counterstarts at0. Inc()increases the count by exactly 1.Add(delta)addsdelta, including negative deltas —Add(-2)decreases the count by 2.Value()reports the current count without changing it.
For example:
var c Counter c.Inc() c.Inc() c.Add(10) fmt.Println(c.Value()) // 12IncandAddmutate the counter, so they take pointer receivers;Valueonly reads, so a value receiver is enough. Keep the signatures exactly as given.package main import "fmt" // Counter counts things. Its zero value is ready to use. type Counter struct{ n int } // Inc adds 1 to the count. func (c *Counter) Inc() { // TODO: implement } // Add adds delta to the count. delta may be negative. func (c *Counter) Add(delta int) { // TODO: implement } // Value returns the current count. func (c Counter) Value() int { // TODO: implement return 0 } func main() { var c Counter c.Inc() c.Inc() c.Add(10) fmt.Println(c.Value()) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
- A zero-value