Embedding instead of inheritance

Embed a struct

Embedding places one struct inside another without a field name. The outer struct then reads the inner fields as if they were its own, which is the Go replacement for simple reuse through inheritance.

package main import "fmt" type Person struct { Name string } type Employee struct { Person ID int } func main() { e := Employee{Person: Person{Name: "Ann"}, ID: 7} fmt.Println(e.Name, e.ID) }

Employee has no Name field of its own, yet e.Name reaches into the embedded Person. When two embedded structs expose the same field name, spell out the path, as in e.Person.Name.

Promoted methods

Methods of the embedded struct are promoted to the outer struct automatically. Callers use the short path, while the method still runs with the inner value as its receiver.

package main import "fmt" type Person struct { Name string } func (p Person) Greet() string { return "hello, I am " + p.Name } type Employee struct { Person ID int } func main() { e := Employee{Person: Person{Name: "Ann"}, ID: 7} fmt.Println(e.Greet()) }

Greet is defined once on Person, but e.Greet works directly on the employee. Promotion also carries interfaces along: an Employee satisfies every interface its embedded Person satisfies.

Shadow a method

When the outer struct defines a method with the same name, the outer one wins and the inner one is shadowed. The hidden version stays reachable through the explicit embedded field, so you can wrap it instead of losing it.

package main import "fmt" type Person struct { Name string } func (p Person) Greet() string { return "hello, I am " + p.Name } type Employee struct { Person ID int } func (e Employee) Greet() string { return fmt.Sprintf("employee %d says: %s", e.ID, e.Person.Greet()) } func main() { e := Employee{Person: Person{Name: "Ann"}, ID: 7} fmt.Println(e.Greet()) fmt.Println(e.Person.Greet()) }

The outer Greet shadows the promoted one, then reuses it through e.Person.Greet. This pattern mirrors overriding with a call to super, minus the class hierarchy.

Compose, do not inherit

Embedding shines when you assemble behavior from small parts instead of growing a deep tree. Give each struct one job, embed the pieces you need, and let the outer type stay thin.

package main import "fmt" type Logger struct{} func (Logger) Log(msg string) { fmt.Println("log:", msg) } type Store struct { items int } func (s *Store) Add() { s.items++ } type Service struct { Logger Store } func main() { svc := Service{} svc.Log("start") svc.Add() fmt.Println(svc.items) }

Service combines a Logger and a Store with two short lines and no base class. Prefer this flat composition: it keeps each piece testable and every dependency visible.

Next: Generics

Article author: Arthur Isaev