8. Structs and methods: value vs. pointer receivers, embedding
8.1 Struct basics and tags
type Server struct {
Host string `json:"host"`
Port int `json:"port"`
TLS bool `json:"tls,omitempty"`
}
Struct tags are metadata strings parsed by reflection at runtime
(encoding/json, encoding/xml, validators, ORMs all read them) — they
have zero effect on the compiler beyond being valid string literals.
8.2 Value vs. pointer receivers — the rule that matters
type Counter struct{ n int }
func (c Counter) IncByValue() { c.n++ } // mutates a COPY — no visible effect
func (c *Counter) IncByPointer() { c.n++ } // mutates the real struct
Rule of thumb: if any method on a type needs a pointer receiver
(to mutate state, or to avoid copying something large), make all
methods on that type use pointer receivers, for consistency and because
Go's method set rules mean a value of type T only gets the pointer
receiver methods promoted automatically when it is addressable — a map
value, for instance, is not addressable and this trips people up:
counters := map[string]Counter{"a": {}}
counters["a"].IncByPointer() // compile error — map value not addressable
8.3 Embedding: composition, not inheritance
type Logger struct{ Prefix string }
func (l Logger) Log(msg string) { fmt.Println(l.Prefix, msg) }
type Server struct {
Logger // embedded — promotes Log() onto Server
Host string
}
s := Server{Logger: Logger{Prefix: "[server]"}, Host: "localhost"}
s.Log("starting up") // calls the embedded Logger's method directly
Embedding promotes fields and methods to the outer type, but it is not
polymorphism — there's no dynamic dispatch, no super, and the embedded
type has no idea it's embedded. It's syntactic sugar for "has-a with
automatic delegation," which is exactly the composition-over-inheritance
philosophy Go pushes everywhere.
8.4 Real-world example
Day 5 of the 90-day plan asks you to embed a Logger struct into a
Server struct — this is the idiom behind virtually every Go SDK's
"client with options" pattern: an HTTP client embeds a base transport,
adds retry/auth logic, and callers use it exactly like the thing it
wraps, because embedding promoted all the underlying methods.