Embedding declares a field using only a type name, with no field name of its own. The embedded type’s fields and methods are then “promoted” to the outer struct, so they can be accessed as if they belonged to it directly. This is Go’s deliberate substitute for classical inheritance: it’s plain composition under the hood (the outer struct literally contains the inner one as a field), not a subtype relationship — an embedded type’s methods don’t know about the struct embedding them, and there’s no polymorphism via embedding alone.
Field and method promotion
An embedded type's fields and methods become accessible directly on the outer type, without an extra level of dot notation:
type Base struct {
Name string
}
func (b Base) Describe() string {
return "I am " + b.Name
}
type Employee struct {
Base // embedded: no field name, just the type
Salary int
}
e := Employee{Base: Base{Name: "Alice"}, Salary: 50000}
fmt.Println(e.Name) // promoted field, no e.Base.Name needed
fmt.Println(e.Describe()) // promoted methodIt's composition, not inheritance
The distinction matters in practice: if Describe were changed to call another method that Employee overrides, the embedded Base.Describe would still call Base's version, not Employee's — there's no dynamic dispatch the way a real subclass would have. You can still access the embedded value explicitly via its type name (e.Base), and if the outer type declares its own field or method with the same name, that shadows the promoted one.
Interface embedding
Interfaces can embed other interfaces too, composing a larger interface from smaller ones — this is how the standard library builds up io.ReadWriter from io.Reader and io.Writer without repeating method signatures.
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface { // composed from both
Reader
Writer
}