Composing Interfaces by Embedding

Interface embedding is the most straightforward form of embedding in Go because interfaces only declare method sets—they carry no data or implementation of their own. Its value lies in composition: instead of restating methods, an interface can simply require another interface as part of its contract.

The canonical illustration comes from the standard library. With io.Reader and io.Writer available, one route to a combined interface is to spell out each method explicitly:

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

That works but duplicates declarations and obscures the relationships between interfaces. A reader of ReadWriter can't tell at a glance how it relates to io.Reader or io.Writer. The standard library avoids this repetition in a number of compositional interfaces—for instance, io.ReadCloser, io.WriteCloser, io.ReadWriteCloser, io.ReadSeeker, and io.WriteSeeker—by embedding:

type ReadWriter interface {
  Reader
  Writer
}

The declaration states intent explicitly: anything that implements ReadWriter must implement both Reader and Writer.

Overlapping Methods After Go 1.14

Interface embedding composes method sets as a union. If interfaces A, B, C, and D are related so that B embeds A, C embeds B, and D embeds C, then the method set of D includes Amethod(), Bmethod(), Cmethod(), and Dmethod().

Trouble arises when the same method arrives through two different embedding chains. If C directly declares Amethod() in addition to embedding B—where B itself embeds A—then prior to Go 1.14, a type embedding both would fail to compile with "Duplicate method Amethod". That restriction was lifted in Go 1.14 through the overlapping interfaces proposal. Since then, the method set of an interface is simply the union of the method sets of everything it embeds, plus its own declared methods.

A practical consequence appears with io.ReadWriteCloser. Its existing standard-library definition is:

type ReadWriteCloser interface {
  Reader
  Writer
  Closer
}

But because io.ReadCloser and io.WriteCloser both include Close(), the more concise embedding form was not legal before Go 1.14:

type ReadWriteCloser interface {
  io.ReadCloser
  io.WriteCloser
}

That form is valid now and expresses the composition more tightly.

Embedding the Built-in error

The net package's error type demonstrates that an interface can embed a non-interface type’s interface as well, including the built-in error:

// An Error represents a network error.
type Error interface {
  error
  Timeout() bool   // Is the error a timeout?
  Temporary() bool // Is the error temporary?
}

Embedding the standard error interface here signals immediately that any net.Error value qualifies as an error. Without the embedding, a programmer would have to find an Error() string method declaration and confirm by hand that it matches the canonical signature.

Compounding Requirements with heap.Interface

The container/heap package places a strong requirement on client types: they must implement sort.Interface as well as the heap-specific methods.

type Interface interface {
  sort.Interface
  Push(x interface{}) // add x as element Len()
  Pop() interface{}   // remove and return element Len() - 1.
}

Without embedding, the same declaration becomes a long list of six methods that hides the underlying dependency on sorting:

type Interface interface {
  Len() int
  Less(i, j int) bool
  Swap(i, j int)
  Push(x interface{}) // add x as element Len()
  Pop() interface{}   // remove and return element Len() - 1.
}

The embedded version makes the contract explicit: to satisfy heap.Interface, a type must first be a full sort.Interface. Spotting that requirement from the expanded method list requires pattern-matching Len(), Less(), and Swap() against sort.Interface’s definition—a task the embed removes entirely.