What is the difference between embedding a struct and embedding an interface?

5 minadvancedgoembeddinginterfacesstructs

Quick Answer

Embedding a struct promotes that struct's fields and concrete methods into the outer type, giving you field/method reuse with real underlying data. Embedding an interface inside a struct promotes only the interface's method signatures, with no implementation — the embedding struct must supply a concrete value satisfying that interface (often set at construction time) for those methods to actually work when called, or they'll panic on a nil interface at runtime. Embedding an interface inside another interface is different again: it's purely additive composition, merging the embedded interface's method set into the new, larger interface, with no runtime object involved at all. All three uses share the same embed-by-type-name-only syntax, but they mean different things depending on what's embedded and where.

Detailed Answer

Go reuses the same embedding syntax for three genuinely different situations, which is a common source of confusion.

1. Embedding a struct in a struct: real fields and methods

type Base struct{ ID int }
func (b Base) Describe() string { return fmt.Sprintf("ID: %d", b.ID) }

type Widget struct {
    Base
    Name string
}
w := Widget{Base: Base{ID: 1}, Name: "gadget"}
fmt.Println(w.Describe())  // uses Base's real implementation

2. Embedding an interface in a struct: no implementation supplied

type Logger interface {
    Log(msg string)
}

type Service struct {
    Logger        // embedded interface — no concrete implementation here
    Name string
}

var s Service
s.Log("hi")   // panics! s.Logger is nil — no concrete value was ever assigned
s := Service{Logger: myConcreteLogger{}, Name: "billing"}
s.Log("hi")   // works, because Logger now holds a real value

This pattern is often used to satisfy an interface with only some methods actually implemented, letting the embedded interface field supply defaults or stubs for the rest, but it's a footgun if you forget to assign a real value before calling a promoted method.

3. Embedding an interface in an interface: pure composition

type Reader interface{ Read(p []byte) (int, error) }
type Writer interface{ Write(p []byte) (int, error) }
type ReadWriter interface {
    Reader
    Writer
}

No runtime object is involved at all here — this just merges method sets to define a bigger interface out of smaller ones, exactly as covered in the earlier composition question.

The key distinction to remember

Struct-in-struct embedding gives you working code for free. Interface-in-struct embedding gives you promoted method signatures with no implementation, requiring you to supply a real value. Interface-in-interface embedding is compile-time-only composition with nothing to instantiate at all.

Related Resources