Day 022 — Struct Embedding & Composition¶
Month 1 · Week 4 · ⬅ Day 021 · Day 023 ➡ · Journal index
🎯 Learning Objective¶
Use struct (and interface) embedding to compose behavior, and explain why Go favors composition over inheritance.
📚 Topics¶
- Embedded fields (anonymous fields) · field & method promotion
- Embedding interfaces in interfaces and in structs
- Name collisions / shadowing · the outer type "wins"
- Composition over inheritance — no subtyping, no
super
📖 Reading / Sources¶
- Learning Go (Bodner) ch.7 — "Types, Methods, and Interfaces" (embedding)
- Effective Go — Embedding
- Go spec — Struct types (embedded fields)
📝 Notes¶
- An embedded field is a field declared with a type but no name:
type Admin struct { User; level int }. The field's name is the unqualified type name (a.User). - Promotion: the embedded type's exported fields and methods become accessible on the outer type as if declared there —
a.Nameanda.Greet()work even though they live onUser. Connects to [[methods]] and method sets. - Embedding is not inheritance: there is no
is-asubtyping. AnAdminis not aUser; you cannot passAdminwhereUseris required (but you can where an interface satisfied by the promoted methods is required). - A pointer-receiver method on the embedded type is promoted to the outer type's pointer method set — same method-set rules as [[methods]] apply.
- Name collision rule: if the outer struct declares a field/method with the same name, the outer one shadows the embedded one. The embedded member is still reachable via the qualified path (
a.User.Name). A collision at the same depth between two embeds is only an error if you actually use the ambiguous selector. - Embedding an interface in a struct stores a value satisfying that interface and promotes its methods — handy for decorators/wrappers (override one method, delegate the rest).
- Embedding interfaces in interfaces composes contracts:
io.ReadWriteris justinterface { Reader; Writer }. Connects to [[interfaces]].
💻 Code Examples¶
type User struct{ Name string }
func (u User) Greet() string { return "hi, I'm " + u.Name }
type Admin struct {
User // embedded: promotes Name and Greet
Level int
}
a := Admin{User: User{Name: "Ada"}, Level: 9}
fmt.Println(a.Name) // promoted field
fmt.Println(a.Greet()) // promoted method
fmt.Println(a.User.Name) // explicit path still works
Full code:
examples/month-01/embedding/main.go· Run:go run ./examples/month-01/embedding
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
| Trace promotion & shadowing in the embedding example | ✅ | examples/month-01/embedding |
| Build a logging decorator by embedding an interface | ✅ | examples/month-01/embedding |
🐛 Mistakes Made¶
- Tried to pass an
Adminto a function takingUser— compile error. Embedding is not subtyping; I passeda.User(or an interface) instead. - Expected the embedded method to see the outer struct's overriding method (virtual dispatch). It doesn't — Go has no dynamic dispatch between embedder and embedded; the embedded method only ever sees its own receiver.
❓ Open Questions¶
- When does deep/multi-level embedding hurt readability vs. an explicit named field? (Lean toward a named field once you need to be explicit about the relationship.)
🧠 Active Recall (answer without looking)¶
-
Q: If
AdminembedsUserand both defineString(), which one doesa.String()call?
A
The outer `Admin.String()` — the outer type shadows the promoted method. `User`'s version is still reachable as `a.User.String()`. -
Q: Does embedding
UsermakeAdminassignable to avar u User?
A
No. Embedding is composition, not inheritance — there's no subtype relationship. You'd assign `a.User`. `Admin` *can* satisfy an interface via the promoted methods, though.
🪶 Feynman Reflection¶
Embedding is "borrowing" a type's fields and methods by value: the outer struct gets a free, anonymous slot of the inner type, and the inner's exported members shine through ("promotion"). It looks like inheritance but it's really just delegation with auto-generated forwarding — there's no parent/child, no super, no virtual methods.
🕳️ Knowledge Gaps¶
- Method-set interaction when embedding a pointer vs a value — re-derive with the methods rules.
✅ Summary¶
I can embed types to promote fields/methods, reason about shadowing, embed interfaces to build decorators, and articulate why Go prefers composition over inheritance.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 023: generics — type parameters, constraints, and
comparable.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): struct embedding and composition (day 022)