It is field promotion with a compiler-generated forwarding rule, not
inheritance. The embedded type becomes a real field named after the type, and its
exported fields and methods can be reached without naming that field, so
s.Logger.Log may also be written s.Log.
type Logger struct{ Prefix string }
func (l Logger) Log(msg string) { fmt.Println(l.Prefix, msg) }
type Server struct {
Logger
Addr string
}
s := Server{Logger: Logger{Prefix: "srv"}, Addr: ":8080"}
s.Log("starting")
The part that separates embedding from inheritance is what happens when the
embedded type calls a method the outer type has "overridden".
type Logger struct{ Prefix string }
func (l Logger) Log(msg string) { fmt.Println(l.Prefix, msg) }
func (l Logger) Warn(msg string) { l.Log("WARN " + msg) }
type Server struct{ Logger }
func (s Server) Log(msg string) { fmt.Println("server:", msg) }
Server{}.Warn("disk full")
There is no dynamic dispatch back into the outer type. Logger.Warn was compiled
against Logger.Log and stays bound to it, so the "override" is invisible from
inside the embedded type. That is the whole difference: embedding delegates
outward, inheritance dispatches inward, and code ported from a language with
virtual methods breaks here silently.
Two further details get asked about. Promotion is shallowest-wins, so a field or
method declared directly on the outer struct shadows the embedded one, and two
embedded types offering the same name at the same depth is not an error until
something actually uses the ambiguous name — at which point it is a compile
error, and you disambiguate by naming the field. Embedding an interface rather
than a struct is the idiom behind partial implementations: embed io.Reader in a
struct and the struct satisfies io.Reader by forwarding to whatever was stored,
which is how test doubles are built with three lines instead of twenty.