What is struct embedding, and how does it enable composition over inheritance?
Quick Answer
Struct embedding places one type inside another without naming a field for it: type Manager struct { Employee; Reports []Employee }. The embedded type's fields and methods get promoted to the outer struct, so m.Name works directly if Employee has a Name field, without writing m.Employee.Name. This is Go's mechanism for code reuse and "is-a"-like behavior without classical inheritance — there's no subclassing, no virtual dispatch, and no super call. If the outer struct defines a method with the same name as a promoted one, the outer struct's method wins, which is how you override embedded behavior. Interfaces can be embedded the same way, letting you compose a larger interface out of smaller ones (as io.ReadWriter does with io.Reader and io.Writer).
Detailed Answer
Go has no class inheritance at all. Embedding is how it gets most of the code-reuse benefit without the complexity that comes with it.
Struct embedding and field/method promotion
type Employee struct {
Name string
ID int
}
func (e Employee) Describe() string {
return fmt.Sprintf("%s (#%d)", e.Name, e.ID)
}
type Manager struct {
Employee // embedded, no field name
Reports []Employee
}
m := Manager{Employee: Employee{Name: "Alice", ID: 1}}
fmt.Println(m.Name) // "Alice" — promoted field, no m.Employee.Name needed
fmt.Println(m.Describe()) // promoted method, works directly on Manager
Overriding a promoted method
func (m Manager) Describe() string {
return fmt.Sprintf("Manager: %s", m.Employee.Describe())
}
fmt.Println(m.Describe()) // uses Manager's own Describe, not Employee's
Defining a method with the same name on the outer type shadows the embedded one. Note there's no super.Describe() equivalent — the overriding method must call m.Employee.Describe() explicitly if it wants the embedded behavior too.
Interface embedding
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader // embeds Reader's method set
Writer // embeds Writer's method set
}
Any type implementing both Read and Write automatically satisfies ReadWriter, with no extra declaration.
Why "composition over inheritance" here
Embedding gives you code reuse and promoted behavior. But the embedded type has no idea it's embedded, there's no polymorphic dispatch through a base-class pointer, and the relationship stays explicit and visible in the struct definition. This avoids the classic fragile-base-class problems of deep inheritance hierarchies in other languages.