Step 1 of 6 · Reading · ~4 min
Learn
Pointers and Methods
Embedding and Composition
By the end of this lesson you can wrap a type so it keeps its behaviour and changes one method, and say which method a call reaches. Go Fundamentals ended its structs lesson with "build big types by composing small ones"; embedding makes that cheap.
Promotion and shadowing
A struct field with a type and no name is embedded. Its fields and methods are promoted: you can reach them directly on the outer value.
Go looks for a name on Truck itself (depth 0), then on its embedded fields (depth 1); the shallowest match wins. So Truck.Name shadows Engine.Name, which stays reachable as t.Engine.Name() because Engine is still an ordinary field.
Study the last line. Inside Label, e is an Engine, so e.Name() is the engine's. There is no virtual dispatch: an "override" changes calls made through the outer value, never calls the inner type makes on itself.
Two embedded types at the same depth with the same method name still compile; only writing the bare name fails, as x.Hello() with ambiguous selector x.Hello. Say x.A.Hello() to pick one.
Embedding an interface
A struct can embed an interface. It then has every method of that interface, forwarded to whatever value is stored in the field, and you declare only the ones you want to change:
Doubled is a Sensor, wrote one method, and got Unit free; d.Sensor.Read() is the delegation. The trap is the zero value: Doubled{} holds a nil Sensor, compiles, and the first promoted call panics with invalid memory address or nil pointer dereference.
Interfaces embed interfaces too: interface { Reader; Writer } is the union of both method sets. The smallest standard one is io.Writer, Write(p []byte) (n int, err error); os.Stdout has it, and fmt.Fprintln(w, ...) makes one call to w.Write per line.
Embedding T or *T
The pointer-receiver rule applies: Add is promoted to *ByVal only, while embedding *Tally promotes it to both.
Promotion can change printing
fmt finds promoted methods too:
Event got String promoted, so Println prints only the stamp and Msg vanishes. Error() behaves the same way. Give the outer type its own String if you want both.
Common mistakes
- A value receiver on the wrapper that keeps the state.
func (t Tally) Add()increments a copy, so the caller still reads0. - Delegating to yourself. Calling
d.Read()insideDoubled.Readrecurses until the stack overflows; go through the field,d.Sensor. - Expecting the inner type to see your override.
t.Label()prints[engine]. - Ignoring stacking order. A wrapper that changes the data changes what the next one sees. Upper-casing
ıturns 2 bytes into 1, so a byte total taken before the upper-casing differs from one taken after.
Your exercise
The program builds a stack of writers from its first input line and writes text through it. Both wrapper types embed io.Writer, so until you give each its own Write they compile and pass text through untouched. The likeliest mistake is the first bullet above; it shows up as bytes=0 lines=0.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…