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

The rule to memorise The method set of *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.

type Email struct{...} func (e *Email) FieldMap() ... ← pointer receiver var _ database.Entity = &Email{} ✅ *Email has FieldMap → satisfies var _ database.Entity = Email{} ❌ Email (value) does NOT have FieldMap

Your code proves the rule

Our entities implement database.Entity with pointer receivers, so the codebase always passes *Email (and stores type Emails []*Email):

internal/spike/modules/email/domain/model/email.go · infrastructure/repositories/email.go
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 { ... }
Why the codebase is consistent Because 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.)
The convenience that hides the rule On an addressable value, Go auto-takes the address: 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

Do this next

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…

Pointer-receiver method → only *Email's method set has it → only the pointer satisfies the interface. Hence []*Email.
Why does our repo store []*Email and never []Email?
recall, then click to reveal
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.
Want to see the exact spec wording on addressability, or a case where a value receiver is the right call (immutability, map values)? Ask me.

1. Go spec — method sets.

2. Effective Go — pointers vs values.