Embedding
Composition over inheritance
Section titled “Composition over inheritance”Go has no extends, no base class, and no virtual dispatch. Instead it has embedding: you place one type inside another without a field name, and the outer type gains all of the inner type’s fields and methods as if they were its own.
type Named struct { Name string}
func (n Named) Describe() string { return "Name: " + n.Name }
type Employee struct { Named // embedded — no field name Department string}Now Employee has a Name field and a Describe() method promoted from Named:
e := Employee{Named: Named{Name: "Alice"}, Department: "Engineering"}fmt.Println(e.Name) // promoted fieldfmt.Println(e.Describe()) // promoted methodHow promotion works
Section titled “How promotion works”When you access e.Name, Go looks for a field or method named Name directly on Employee first. If none is found, it descends into each embedded type in order. This lookup is purely syntactic sugar — the long form e.Named.Name is always valid too.
You can override a promoted method by declaring a method with the same name on the outer type:
func (e Employee) Describe() string { return fmt.Sprintf("%s in %s", e.Name, e.Department)}Now e.Describe() calls the outer method. The inner method is still reachable via e.Named.Describe().
Interface embedding
Section titled “Interface embedding”Interfaces can also embed other interfaces:
type Stringer interface { String() string}
type Describer interface { Stringer // embeds Stringer Describe() string}Any type that satisfies Describer also satisfies Stringer automatically. The standard library uses this extensively: io.ReadWriter embeds io.Reader and io.Writer.
Embedding is not inheritance
Section titled “Embedding is not inheritance”A critical distinction:
| Inheritance (Java/C++) | Embedding (Go) |
|---|---|
| Virtual dispatch: method call on outer type can dispatch to inner’s override | No virtual dispatch: calling the inner method always calls that method, regardless of what is on the outer type |
instanceof / type hierarchy | No hierarchy — types are unrelated |
| Base class can reference derived class via virtual methods | Embedded type has no knowledge of the outer type |
Because there is no virtual dispatch, embedding does not allow polymorphism on the outer type. To get polymorphism, you still need an interface.
package main
import "fmt"
type Named struct { Name string}
func (n Named) Describe() string { return "Name: " + n.Name}
type Employee struct { Named Department string}
// Interface embeddingtype Stringer interface { String() string}
type Describer interface { Stringer Describe() string}
func (e Employee) String() string { return fmt.Sprintf("%s (Dept: %s)", e.Name, e.Department)}
func print(d Describer) { fmt.Println(d.String()) fmt.Println(d.Describe())}
func main() { e := Employee{ Named: Named{Name: "Alice"}, Department: "Engineering", } // promoted field and method fmt.Println(e.Name) fmt.Println(e.Describe()) print(e)}Loading Go runtime (first run only, ~8 MB)…