Go has no inheritance; embedding is how you reuse behaviour and build bigger interfaces from small ones.
A struct can embed another type by listing it without a field name:
type Logger struct{ prefix string }
func (l *Logger) Log(msg string) { fmt.Println(l.prefix + msg) }
type Service struct {
*Logger
name string
}The embedded type's fields and methods are promoted: svc.Log("hi") works as if Service declared Log, and Service now satisfies any interface that Log helps satisfy. The embedded value is still a normal field, named after its type: svc.Logger.
This is composition, not inheritance:
- Promoted methods run with the embedded value as receiver. Inside
Log,lis the*Logger, and it knows nothing about the outerService. There's no virtual dispatch back to the outer type. - An outer method or field with the same name shadows the promoted one; you can still call
svc.Logger.Logexplicitly. - If two embedded types at the same depth provide the same name, using that name is a compile error (ambiguous selector) — but only when you actually use it.
Interfaces embed interfaces to build larger contracts: io.ReadWriter is interface{ Reader; Writer }.
Embedding an interface in a struct is a useful trick for wrapping: the struct satisfies the whole interface, and you override only the methods you care about. It's common in tests and decorators, but calling a non-overridden method on a nil embedded interface panics.
Be careful embedding types in exported structs: every promoted method becomes part of your public API — including embedding sync.Mutex, which exports Lock and Unlock.
Example
package main
import (
"fmt"
"io"
"strings"
)
// countingReader wraps any io.Reader and overrides only Read.
type countingReader struct {
io.Reader // embedded interface
n int
}
func (c *countingReader) Read(p []byte) (int, error) {
n, err := c.Reader.Read(p) // call the wrapped reader explicitly
c.n += n
return n, err
}
func main() {
cr := &countingReader{Reader: strings.NewReader("hello, embedding")}
data, _ := io.ReadAll(cr)
fmt.Println(string(data), cr.n) // hello, embedding 16
}Remember
- Embedded fields' methods are promoted to the outer type
- Promoted methods see only the inner value — no virtual dispatch
- Embed interfaces to compose contracts or to wrap and override
Watch out for
- Embedding a type in an exported struct and accidentally exposing its methods (e.g. Lock/Unlock) as API.