Methods, receivers, and embedding
The rule about receivers that most beginners trip on.
Topic 16 of 98 · 5 min to read · 45 min to build
After this topic you can
- Write a value-receiver method and a pointer-receiver method, and explain when to use each.
- Write an enum with a named type,
iotaand aString()method. - Explain why embedding is composition and not inheritance.
Methods are functions with a receiver
A method is a function with an extra parameter written before the name. That parameter is the receiver, and it comes in two flavours that behave very differently.
methods.go
// VALUE receiver — gets a copy. Cannot change the original.func (u User) Display() string { return u.Name + " <" + u.Email + ">"}// POINTER receiver — can change the original.func (u *User) Rename(n string) { u.Name = n}u := User{Name: "old"}u.Rename("new") // Go takes the address for youfmt.Println(u.Name) // "new"Enums: a named type, iota, and a String method
In Constants and iota you numbered choices with iota. Give those constants their own named type and a String() method, and fmt prints them as words. fmt looks for a method called String() string and uses it automatically; Interfaces explains why that works.
enum.go
type Status int // a new type whose underlying value is an intconst ( Pending Status = iota // 0 Active // 1 Deleted // 2)func (s Status) String() string { switch s { case Pending: return "pending" case Active: return "active" } return "deleted"}fmt.Println(Active) // active — String() at workfmt.Printf("%d\n", Active) // 1 — the number underneathWhich receiver do I use?
| Use a pointer receiver when | Use a value receiver when |
|---|---|
| The method changes the struct | The type is small and you never mutate it |
| The struct is large (copying costs) | Each caller should get its own independent copy |
The struct contains a sync.Mutex | The type is naturally a value, like time.Time |
| Any other method already needs one |
Watch out: Be consistent. Mixing value and pointer receivers on one type draws review comments, and it leads to the confusing "does not implement" error in the Interfaces topic: a
T value only has the value-receiver methods, so it fails any interface that needs a pointer-receiver one. If one method needs a pointer, make them all pointers.The same functions, written as methods
In Structs you wrote display(u) and rename(&u, "new"). Move the struct parameter in front of the name and they become methods: u.Display() and u.Rename("new"). The *User parameter becomes a pointer receiver.
Embedding promotes methods too
embed.go
type Base struct{ ID int }func (b Base) Describe() string { return fmt.Sprint("id=", b.ID) }type Admin struct { Base // embedded — no field name Level int}a := Admin{Base: Base{ID: 1}, Level: 5}fmt.Println(a.Describe()) // promoted methodNote: This is not inheritance. An
Admin is not a Base: you cannot pass one where the other is expected, and Describe cannot see Admin.Level. There are no virtual methods in Go. When you want one type to stand in for another, you need an interface, which is the next topic.The short version
- A value receiver gets a copy; a pointer receiver can mutate the original.
- Pointer receiver when you mutate, the struct is large, or it holds a mutex. Then use it for all methods.
- Embedding promotes fields and methods, but it is composition, not inheritance.
- An
Adminis not aBase. There are no virtual methods in Go.
Remember: If one method needs a pointer receiver, make them all pointer receivers.
Build this before moving on
Give a Rectangle an Area() value receiver and a Scale() pointer receiver, then prove calling Scale on a copy leaves the original alone.Coding challenge · checked by tests
Give
Rect two methods. Area() (value receiver) returns width times height. Scale(f float64) (pointer receiver) multiplies both sides by f, changing the original. Example: Rect{W: 2, H: 3}.Area() → 6; after r.Scale(2), r is {W: 4, H: 6}.Tab indents. Press Esc, then Tab, to leave the editor.
Then try these in your own editor
- 1Embed one struct in another and call a promoted method.
- 2Try passing the outer type where the embedded one is expected, and read the error.
- 3Mix value and pointer receivers on one type and see what
go vetsays.
Official documentation