24. Embedding: composition instead of inheritance
Go has no extends. What it has is embedding: put a type inside a struct
without giving it a field name, and its fields and methods are promoted to
the outer type. It looks like inheritance from the outside and behaves much
more predictably.
A named field vs an embedded type
package main
import "fmt"
type Address struct {
City string
Zip string
}
type WithField struct {
Name string
Home Address
}
type WithEmbed struct {
Name string
Address
}
func main() {
a := WithField{Name: "Ada", Home: Address{City: "London", Zip: "E1"}}
b := WithEmbed{Name: "Grace", Address: Address{City: "Arlington", Zip: "22204"}}
fmt.Println(a.Home.City)
fmt.Println(b.City)
fmt.Println(b.Address.City)
}
The only syntactic difference is the missing field name — Address instead
of Home Address. But now b.City works directly: the embedded type's
fields are promoted to the outer struct.
b.Address.City still works too. The promotion is a shortcut, not a merge —
the embedded value is really there, under a field named after its type.
Why this is useful
You pull shared fields into one type and embed it wherever they belong:
package main
import "fmt"
type Meta struct {
ID int
CreatedBy string
}
type Article struct {
Meta
Title string
Words int
}
type Comment struct {
Meta
Body string
}
func main() {
a := Article{
Meta: Meta{ID: 1, CreatedBy: "ada"},
Title: "Slices explained",
Words: 1200,
}
c := Comment{
Meta: Meta{ID: 2, CreatedBy: "grace"},
Body: "Great post!",
}
fmt.Println(a.ID, a.CreatedBy, a.Title)
fmt.Println(c.ID, c.CreatedBy, c.Body)
}
Note the literal: you initialise the embedded value under its type name,
Meta: Meta{...}. Only access is promoted, not construction.
Methods are promoted too
This is where embedding earns its keep. (Methods get a lesson of their own
next — for now, read func (m Meta) Owner() string as "a function attached
to Meta".)
package main
import "fmt"
type Meta struct {
ID int
CreatedBy string
}
func (m Meta) Owner() string {
return fmt.Sprintf("#%d by %s", m.ID, m.CreatedBy)
}
type Article struct {
Meta
Title string
}
func main() {
a := Article{Meta: Meta{ID: 7, CreatedBy: "katherine"}, Title: "Maps"}
fmt.Println(a.Owner())
fmt.Println(a.Title, "-", a.Meta.Owner())
}
Article never declared Owner, but it has one. That's how Go reuses
behaviour — and it's why you'll see sync.Mutex embedded directly in structs
so the outer type gets Lock() and Unlock() for free.
Overriding: the outer type wins
Define a method with the same name on the outer type and it takes priority:
package main
import "fmt"
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
func (a Animal) Describe() string {
return "I am " + a.Name + ". " + a.Speak()
}
type Dog struct {
Animal
}
func (d Dog) Speak() string {
return d.Name + " barks"
}
func main() {
d := Dog{Animal{Name: "Rex"}}
fmt.Println(d.Speak())
fmt.Println(d.Animal.Speak())
fmt.Println(d.Describe())
}
Look hard at the third line: Describe is Animal's method, and it calls
a.Speak() — Animal's Speak, not Dog's. It prints "Rex makes a
sound".
If you come from Java or Python, that's the opposite of what you expect.
There is no virtual dispatch here: Animal has no idea it's embedded in
anything. Embedding is composition — the outer type forwards to the inner
one, and the inner one never calls back up.
When you genuinely need the "call the specific implementation" behaviour, you use an interface, which is the next module. This distinction is the single biggest mental shift for people arriving from class-based languages, so it's worth running that program twice.
Ambiguity is an error, not a guess
Embed two types with the same field and Go refuses to pick:
package main
import "fmt"
type A struct{ Name string }
type B struct{ Name string }
type C struct {
A
B
}
func main() {
c := C{A: A{Name: "from A"}, B: B{Name: "from B"}}
// fmt.Println(c.Name) // won't compile: ambiguous selector
fmt.Println(c.A.Name, c.B.Name)
}
The promotion simply doesn't happen for the ambiguous name; you must say which one you mean. No resolution order to memorise, no surprises — exactly the kind of multiple-inheritance headache Go's design avoids.
Embedding pointers and interfaces
You can embed a pointer type or an interface as well:
package main
import "fmt"
type Logger struct {
Prefix string
}
func (l *Logger) Log(msg string) {
fmt.Println(l.Prefix + msg)
}
type Service struct {
*Logger
Name string
}
func main() {
s := Service{Logger: &Logger{Prefix: "[svc] "}, Name: "billing"}
s.Log("started")
s.Prefix = "[billing] "
s.Log("ready")
}
Embedding *Logger shares one logger rather than copying it — several
services can point at the same instance. Embedding an interface is rarer but
powerful: it lets your type satisfy that interface by forwarding, and you
override only the methods you care about.
Your turn
Build a Manager type that embeds an Employee (with Name and Salary)
and adds a Reports int. Print the manager's name and salary through the
promoted fields:
Grace earns 120000 and manages 4 people
package main
import "fmt"
type Employee struct {
Name string
Salary int
}
// define Manager, embedding Employee
func main() {
m := Manager{Employee: Employee{Name: "Grace", Salary: 120000}, Reports: 4}
fmt.Printf("%s earns %d and manages %d people\n", m.Name, m.Salary, m.Reports)
}
package main
import "fmt"
type Employee struct {
Name string
Salary int
}
type Manager struct {
Employee
Reports int
}
func main() {
m := Manager{Employee: Employee{Name: "Grace", Salary: 120000}, Reports: 4}
fmt.Printf("%s earns %d and manages %d people\n", m.Name, m.Salary, m.Reports)
}
Next: attaching behaviour to your types properly — methods.