Skip to content

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

📝 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.Name and a.Greet() work even though they live on User. Connects to [[methods]] and method sets.
  • Embedding is not inheritance: there is no is-a subtyping. An Admin is not a User; you cannot pass Admin where User is 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.ReadWriter is just interface { 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 Admin to a function taking User — compile error. Embedding is not subtyping; I passed a.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)

  1. Q: If Admin embeds User and both define String(), which one does a.String() call?

    A The outer `Admin.String()` — the outer type shadows the promoted method. `User`'s version is still reachable as `a.User.String()`.

  2. Q: Does embedding User make Admin assignable to a var 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)