Struct Embedding
Go uses struct embedding instead of inheritance - embedding promotes another type's fields and methods to the outer struct.
Go has no inheritance. Instead, you embed one struct type inside another - the embedded type's fields and methods are promoted to the outer struct. This is composition, not inheritance: the embedded type retains its own identity and the outer struct does not become a subtype.
Embed a struct by including it as an unnamed field. The outer struct gains access to all of the embedded type's exported fields and methods as if they were defined directly on it.
package main
import "fmt"
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
type Dog struct {
Animal // embedded - no field name
Breed string
}
func main() {
d := Dog{
Animal: Animal{Name: "Rex"},
Breed: "Labrador",
}
fmt.Println(d.Name) // promoted from Animal
fmt.Println(d.Speak()) // promoted from Animal
fmt.Println(d.Breed)
}The outer struct can define its own method with the same name to override the promoted one. The embedded type's version is still accessible via the full field path.
package main
import "fmt"
type Base struct{}
func (b Base) Describe() string { return "I am Base" }
type Extended struct {
Base
label string
}
func (e Extended) Describe() string {
return "I am Extended (" + e.label + ")"
}
func main() {
e := Extended{label: "custom"}
fmt.Println(e.Describe()) // I am Extended (custom)
fmt.Println(e.Base.Describe()) // I am Base - still accessible
}Embedding interfaces inside structs is a common pattern for building partial implementations or mock types. The embedded interface provides default method stubs; the outer struct overrides only the methods it needs.
package main
import "fmt"
type Logger interface {
Log(msg string)
Prefix() string
}
// DiscardLogger satisfies Logger but does nothing
type DiscardLogger struct{}
func (d DiscardLogger) Log(msg string) {}
func (d DiscardLogger) Prefix() string { return "" }
type ServiceLogger struct {
DiscardLogger // inherit the no-op Log
prefix string
}
// Override only Prefix
func (s ServiceLogger) Prefix() string { return s.prefix }
func emit(l Logger, msg string) {
fmt.Printf("[%s] %s\n", l.Prefix(), msg)
}
func main() {
emit(ServiceLogger{prefix: "svc"}, "started")
}In production
Struct embedding promotes the embedded type's method set to the outer struct, which can accidentally satisfy interfaces you did not intend to implement. In public packages, add compile-time interface checks (var _ InterfaceName = MyType{}) to make the intent explicit and catch surprises early. Engineers who come from class-based languages sometimes try to use embedding as inheritance and are surprised when they cannot call a "parent" method from within an "overriding" method - Go has no super. Design your type hierarchies with composition explicitly in mind: embed for reuse, not for subtyping.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free