Dev Skills

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, iota and a String() 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 you
fmt.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 int
const (
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 work
fmt.Printf("%d\n", Active) // 1 — the number underneath
This is how Go does enums. The named type also stops you passing a plain number where a `Status` is expected by mistake.

Which receiver do I use?

Use a pointer receiver whenUse a value receiver when
The method changes the structThe 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.MutexThe 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 method
Note: 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 Admin is not a Base. 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
  1. 1Embed one struct in another and call a promoted method.
  2. 2Try passing the outer type where the embedded one is expected, and read the error.
  3. 3Mix value and pointer receivers on one type and see what go vet says.
Check yourselfTake the Core language test