Embedding & Composition
Prerequisites: Interfaces
Go has no classes and no inheritance. Instead you build bigger types out of smaller ones — composition. Go gives composition a shortcut called embedding: include a type in a struct without a field name, and its fields and methods are promoted so you can use them directly on the outer struct.
Embedding is not inheritance: the outer type is not a subtype of the inner
one, and there is no super. The embedded value is just a field whose name
is its type name.
Syntax
type Service struct {
Logger // embedded: field name is "Logger"
Name string
}
type ReadWriter interface {
Reader // interfaces can embed other interfaces
Writer
}
Example
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 (s Service) Start() {
s.Log("starting " + s.Name) // promoted method from Logger
}
type Base struct{}
func (Base) Describe() string { return "base" }
type Special struct {
Base
}
// Describe on Special shadows the promoted Base.Describe.
func (Special) Describe() string { return "special" }
type Describer interface {
Describe() string
}
func main() {
svc := Service{Logger: Logger{Prefix: "[svc]"}, Name: "api"}
svc.Start()
svc.Log("direct call")
fmt.Println(svc.Prefix) // promoted field
fmt.Println(svc.Logger.Prefix) // the same field, spelled out
sp := Special{}
fmt.Println(sp.Describe())
fmt.Println(sp.Base.Describe()) // the embedded method is still reachable
var d Describer = Base{} // Base satisfies Describer...
fmt.Println(d.Describe())
d = sp // ...and so does Special
fmt.Println(d.Describe())
}
Expected output
[svc] starting api
[svc] direct call
[svc]
[svc]
special
base
base
special
Because promoted methods become part of the outer type's method set, a
Service satisfies any interface that Logger satisfies. But you cannot
assign a Service to a variable of type Logger — they are different types.
Common mistakes
- Expecting "virtual" behaviour: when
Logger.Logruns, it only knows about theLogger, never theServicethat contains it. Methods on the embedded type cannot call "overridden" methods of the outer type. - Embedding a type only to reuse one method, which also exposes all of its
other exported fields and methods. A named field (
log Logger) keeps the API of the outer type small. - Embedding two types that both have a method with the same name, then calling it on the outer type — the call is ambiguous and won't compile unless the outer type defines its own method.
Practice
Create an Animal struct with a Name field and a method
Hello() string returning "Hi, I'm <Name>". Create a Dog struct that
embeds Animal and adds a Breed field. Print d.Hello() and d.Breed.
Show solution
package main
import "fmt"
type Animal struct {
Name string
}
func (a Animal) Hello() string {
return "Hi, I'm " + a.Name
}
type Dog struct {
Animal
Breed string
}
func main() {
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Beagle"}
fmt.Println(d.Hello()) // Hi, I'm Rex
fmt.Println(d.Breed) // Beagle
}