Lesson 5 · Methods, interfaces & generics
Methods & receivers
The method set — the rule that decides what satisfies an interface.
Your win: explain what a "method set" is, and why a value
T sometimes fails to satisfy an interface that *T satisfies —
the mechanism that connects Lesson 3 to interfaces (Lesson 6).
A method is a function with a receiver
Go has no classes; methods are just functions with a receiver declared before the name. You met the value/pointer choice in Lesson 3 — here's the consequence that trips people up.
func (e *Email) FieldMap() (...) { ... } // pointer receiver
func (f future) Await() Result { ... } // value receiver
The method set rule
*T includes both value-receiver and
pointer-receiver methods. The method set of T (a value) includes
only value-receiver methods.1
Why it matters: an interface is satisfied by a type only if that type's method set
contains all the interface's methods. So if a method has a pointer receiver,
only *T satisfies the interface — a plain T value does not.
Your code proves the rule
Our entities implement database.Entity with pointer
receivers, so the codebase always passes *Email (and stores
type Emails []*Email):
func (e *Email) FieldMap() (fields []string, values []interface{}) { ... }
func (*Email) TableName() string { return "emails" } // blank receiver: unused
// Repos are called on *EmailRepo, entities passed as *Email:
func (repo *EmailRepo) UpsertEmail(ctx, db, e *Email) error { ... }
FieldMap/Add use pointer receivers, a value
Email{} could never satisfy database.Entity — so everything
is a *Email. This is the method-set rule quietly forcing a convention.
(A tiny value type like future uses a value receiver because a copy is
fine and it never needs to satisfy a pointer-only interface.)
e.FieldMap()
works even if e is an Email, because the compiler rewrites
it to (&e).FieldMap(). But that shortcut does not
apply to interface satisfaction — that's why the value still fails the
var _ database.Entity = Email{} check.
Consistency guidance
Effective Go's advice, which our repo follows: if any method of a type needs a pointer receiver, give all its methods pointer receivers, so the type's method set is uniform and there's no confusion about what satisfies what.2
A Tour of Go — "Methods and interfaces"
Work through the receiver pages, then the interface pages. Effective Go's "Pointers vs. Values" section states the method-set rule precisely.
→ go.dev/tour — methods & interfaces
→ Effective Go — pointers vs values
Check yourself (from memory)
Q1. The method set of a value T contains…
T → value methods only; *T →
both. That asymmetry decides interface satisfaction.
Q2. FieldMap has a pointer receiver, so database.Entity is satisfied by…
*Email's
method set has it → only the pointer satisfies the interface. Hence []*Email.
[]*Email and never []Email?Email's methods (FieldMap,
Add) use pointer receivers, so only *Email's method set
contains them — meaning only *Email satisfies database.Entity.
A value Email wouldn't satisfy the interface the DB layer requires, so
the whole codebase uses pointers. The method-set rule enforcing a convention.