In Go, why does o.Apply() compile when o is an Order value but Apply has a pointer receiver?
answer
- the compiler writes the punctuation for you
- one precondition, and it is about addresses
- &o goes in, *p comes out
- a call-site rewrite, not a type conversion
- a variable has an address, a temporary may not
basics
~10 sBecause the compiler rewrites the call. When the operand is addressable, o.Apply() is shorthand for (&o).Apply(); the reverse shorthand turns p.Total() on a pointer into (*p).Total(). Both are call-site conveniences only.
solid answer
~40 sGo inserts the missing `&` or `*` for you at a method call. If `o` is a variable of type `Order` and `Apply` is declared `func (o *Order) Apply()`, the compiler treats `o.Apply()` as `(&o).Apply()`. Going the other way, if `p` is an `*Order` and `Total` is declared `func (o Order) Total() int64`, then `p.Total()` becomes `(*p).Total()`, which dereferences the pointer and copies the `Order` into the method. The condition for the first rewrite is **addressability**: the compiler must have something whose address it can take — a variable, a field of an addressable struct, an element of a slice, or the result of dereferencing a pointer. That is the whole rule; it is syntax at the call site, not a conversion between `Order` and `*Order`.
code
go · 11 linesfunc (o *Order) ApplyFee(minor int64) { o.TotalMinor += minor }
func (o Order) Total() int64 { return o.TotalMinor }
var ord Order
ord.ApplyFee(250) // compiles as (&ord).ApplyFee(250)
p := &ord
_ = p.Total() // compiles as (*p).Total(), copying the Order
orders := []Order{{}, {}}
orders[1].ApplyFee(250) // slice elements are addressable, so this mutates the slicego deeper
Know that Go lets you call a mutating method on a plain variable without writing an ampersand, and that it works because the compiler adds it for you.
State both rewrites precisely — value operand becomes (&o).M(), pointer operand becomes (*p).M() — and name addressability as the condition for the first. Expect to be asked what gets copied in the second.
Show where the sugar stops helping: a range loop whose element copy gets mutated, a hot path that copies a large struct through a pointer, and the fact that a call site alone never reveals whether it mutates.
Argue for conventions that make mutation legible without reading declarations: one receiver form per type, and method names that announce whether a call changes state.
## The shorthand Go lets you write a method call without matching the receiver form exactly: ```go var o Order o.Apply() // Apply is func (o *Order) Apply(); compiles as (&o).Apply() p := &o amount := p.Total() // Total is func (o Order) Int64; compiles as (*p).Total() ``` Both directions are pure convenience inserted by the compiler at the call site. Without them, Go code would be littered with `(&o)` and `(*p)`, and the language designers decided that noise buys nothing. ## Direction one: value operand, pointer-receiver method `o.Apply()` where `o` is an `Order` and `Apply` needs an `*Order` compiles to `(&o).Apply()`. This is what makes mutating methods pleasant to call — you never write `&` at a call site. The requirement is that `o` be **addressable**, because the compiler literally has to produce an address. Addressable operands include a plain variable, a field of an addressable struct (`ord.Line`), an element of a slice (`orders[i]`), and the result of a pointer dereference (`(*p).Line`). If the compiler cannot form an address for the operand, the shorthand is unavailable and you get a compile error rather than a silent copy — which is the right outcome, since silently mutating a temporary would be a bug factory. This is why a slice element works and a loop variable is a trap for a *different* reason: `orders[i].Apply()` takes the address of the actual element, whereas `for _, o := range orders { o.Apply() }` takes the address of `o`, which is a copy of the element. Both compile. Only the first changes the slice. ## Direction two: pointer operand, value-receiver method `p.Total()` where `p` is an `*Order` and `Total` has receiver `Order` compiles to `(*p).Total()`. The pointer is dereferenced and the whole struct is copied into the method. There is no addressability question here — a dereference always works on a non-nil pointer — but there are two consequences worth naming. First, the copy is real: calling a value-receiver method through a pointer in a hot loop copies the struct every time. Second, the dereference happens before the call, so if `p` is nil the program panics at the call site, before the body runs. ## What the shorthand is not It is easy to conclude from this that `Order` and `*Order` are interchangeable. They are not — the rewrite is a rule about **method call expressions on an addressable operand**, and nothing else. It does not convert one type into the other, it does not apply where the compiler has no address to give, and it is not what governs assigning a value into other kinds of variables. Treat it as sugar with one precondition rather than as a general equivalence, and the surprising cases stop being surprising. ## Reading real code with this in mind A practical consequence: at a call site like `ord.Cancel()` you cannot tell from the call alone whether `ord` is being mutated. The receiver form lives in the declaration, possibly in another file. Two habits compensate. First, keep one receiver form across a whole type, so that knowing the type is enough. Second, when a method mutates, name it so the call site says so — `Cancel`, `Apply`, `Reset` read as mutations; `Total`, `Snapshot`, `String` read as reads. This also explains a class of question from engineers new to Go: 'if I never write `&`, when does a pointer appear?' The answer is that it appears exactly here, silently, and only when the compiler has something addressable to point at.
- What happens at p.Total() when p is a nil *Order and Total has a value receiver?The rewrite is `(*p).Total()`, so the pointer is dereferenced to produce the receiver copy before the body runs, and a nil pointer makes that dereference panic at the call site. The panic is not caused by anything inside Total; the method never starts.
- Why does calling a mutating method inside for _, o := range orders leave the slice unchanged?The rewrite takes the address of `o`, and `o` is a copy of the element that `range` assigned into it — so the method mutates the copy. Index instead: `for i := range orders { orders[i].Apply() }` takes the address of the real element, because slice elements are addressable.
- Does this shorthand mean I can ignore the difference between Order and *Order?No. It is one rule about method calls on an addressable operand. It does not make the two types interchangeable, and where the compiler has no address available the shorthand simply does not apply and the code fails to compile rather than quietly copying.
saying these in an interview costs you the question
- Says Go converts between Order and *Order automatically everywhere
- Thinks o.Apply() copies the value before calling a pointer-receiver method
- Believes an explicit & is required at every mutating call site
- Cannot name addressability as the precondition for the rewrite
- Assumes calling a value-receiver method through a pointer avoids the copy