Go has no inheritance, but it has embedding
Inheritance models an "is-a" relationship: a Puppy is a kind of Dog, so it should get the dog's data and behaviour without copy-pasting. Go has no extends keyword and no class hierarchy. It reuses code by embedding: you put one struct type inside another without giving it a field name.
type Puppy struct {
Dog
}
The line Dog is an anonymous (embedded) field. Its field name is the type name, Dog, and everything in Dog is promoted to Puppy.
What gets promoted
- Fields. If
Doghas anamefield, aPuppyvalue can usep.namedirectly. The longer formp.Dog.namemeans the same thing. - Methods. If
Doghas abarkmethod, you can callp.bark().
What you still write yourself is the child's own extras: new fields, new methods, and a constructor that sets up the embedded part.
Why reuse by embedding
The point of inheritance is to write shared code once. With embedding, the shared name field and bark method exist only in Dog, and every type that embeds Dog gets them. If bark changes, all embedding types change with it. Because the relationship is a plain field, it is also visible in the code: you can read the struct and see exactly what it contains.
Embedding is composition, not a subtype
A Puppy contains a Dog, and Go does not treat it as one. A function that takes a Dog parameter does not accept a Puppy:
func takesDog(d Dog) { fmt.Println(d.name) }
p := Puppy{Dog{"x"}}
takesDog(p) // compile error: cannot use p as Dog value
takesDog(p.Dog) // works
I compiled the first call and got that error. To write code that accepts "anything dog-like", use an interface, which a later lesson covers. A Puppy does satisfy an interface whenever its promoted and own methods match it.
Fields you add, and more than one embed
The outer struct can declare fields of its own next to the embedded type, for example toy string in Puppy. A struct may also embed several types. If two embedded types promote the same name at the same depth, using that name through the outer value is ambiguous and the compiler rejects it until you write the full path, such as p.Dog.name.
Initialising the embedded part
The embedded field is set in a struct literal by using the type name as the key, with a nested literal for its value:
func NewPuppy(name string) Puppy {
return Puppy{Dog: Dog{name: name}}
}
This plays the role that calling a parent constructor plays in other languages. If you leave Dog: ... out, the embedded Dog is simply its zero value, with an empty name.
The lesson's exercise
The starter code has Dog with bark, an empty Puppy struct, and an unfinished NewPuppy.
- Embed
DoginPuppyas an anonymous field, so its fields and methods are promoted. - Finish
NewPuppyby returning aPuppywhoseDogfield is aDogliteral holding the name. - Add
whinewith aPuppyreceiver namedthis, and printwhimperwithfmt.Println. - Write
mainFunction: createbuddywithNewPuppy("Buddy"), call the promotedbark, then callwhine.
type Dog struct {
name string
}
func (this Dog) bark() {
fmt.Printf("Woof! I am %s\n", this.name)
}
type Puppy struct {
Dog
}
func NewPuppy(name string) Puppy {
return Puppy{Dog: Dog{name: name}}
}
func (this Puppy) whine() {
fmt.Println("whimper")
}
func mainFunction() {
buddy := NewPuppy("Buddy")
buddy.bark()
buddy.whine()
}
The output is:
Woof! I am Buddy
whimper
The call buddy.bark() runs Dog's method with this bound to the embedded Dog, not to the whole Puppy. That detail matters for overriding, covered in the next lesson.
Common mistakes
- Naming the field.
dog Dogis an ordinary named field, and nothing is promoted: you would have to writep.dog.bark(). - Writing
Puppy{name: name}. Promoted fields cannot be used as keys in a literal. UsePuppy{Dog: Dog{name: name}}. - Passing a
Puppywhere aDogis required. Passp.Dogor use an interface.
Key takeaways
- Embed a type by listing it without a field name.
- Its fields and methods are promoted to the outer type.
- Build the embedded part with the type name as the key in the literal.
- Embedding is composition: the outer type is not a subtype of the embedded one.