Embedding Interfaces in Structs

Go supports three kinds of embedding: structs in structs, interfaces in interfaces, and interfaces in structs. The last one often looks odd at first, since it isn't obvious what an embedded interface field actually means at runtime. The mechanics turn out to be simple, and the pattern shows up throughout the standard library in several distinct forms.

A minimal example makes the behavior concrete:

type Fooer interface {
  Foo() string
}

type Container struct {
  Fooer
}

Here Fooer is an interface and Container embeds it. As with struct embedding, the embedded interface's methods are promoted to the embedding struct. You can think of Container as having an implicit forwarding method:

func (cont Container) Foo() string {
  return cont.Fooer.Foo()
}

The embedded Fooer field is a regular interface value, assigned when the container is built or later:

// sink takes a value implementing the Fooer interface.
func sink(f Fooer) {
  fmt.Println("sink:", f.Foo())
}

// TheRealFoo is a type that implements the Fooer interface.
type TheRealFoo struct {
}

func (trf TheRealFoo) Foo() string {
  return "TheRealFoo Foo"
}

This allows:

co := Container{Fooer: TheRealFoo{}}
sink(co)

Which prints sink: TheRealFoo Foo. Because the interface is embedded, Container itself implements Fooer, so a Container can be passed anywhere a Fooer is expected. Without embedding, the call sink(co) would not compile.

If the embedded interface field is left unset, it holds the zero value for an interface: nil. Calling a promoted method then panics:

co := Container{}
sink(co)

That covers the mechanism. The more interesting question is why this pattern is useful. Three standard-library examples and one from the wider Go ecosystem show the main ways it gets applied.

Interface Wrapping with Selective Override

A common client-code use case is wrapping an interface to extend one of its methods. Suppose you want a network connection that tracks the number of bytes read. Define a struct that embeds net.Conn:

type StatsConn struct {
  net.Conn

  BytesRead uint64
}

StatsConn now satisfies net.Conn as long as the embedded field is initialized with a real connection. It inherits all methods of that value but lets you intercept any method you want. To count bytes, override Read and delegate to the embedded connection:

func (sc *StatsConn) Read(p []byte) (int, error) {
  n, err := sc.Conn.Read(p)
  sc.BytesRead += uint64(n)
  return n, err
}

From the caller's perspective nothing changes — Read works as before, with additional bookkeeping. Initialization is the key step:

conn, err := net.Dial("tcp", u.Host+":80")
if err != nil {
  log.Fatal(err)
}
sconn := &StatsConn{conn, 0}

net.Dial returns a value implementing net.Conn, which is used to fill the embedded field. The wrapper can then be passed to any function accepting a net.Conn:

resp, err := ioutil.ReadAll(sconn)
if err != nil {
  log.Fatal(err)
}

The alternative without embedding is an explicit field plus forwarding methods for every member of the interface:

type StatsConn struct {
  conn net.Conn

  BytesRead uint64
}

And a forwarder per method, such as:

func (sc *StatsConn) Close() error {
  return sc.conn.Close()
}

net.Conn has eight methods, so writing all those forwarders is tedious. Embedding gives them for free, and only the methods you actually need overridden require definitions.

Reversing a Sort with an Embedded Interface

A classic use of interface-in-struct embedding in the standard library is sort.Reverse. New Go users often find its behavior surprising. Consider ordinary sorting of an integer slice:

lst := []int{4, 5, 2, 8, 1, 9, 3}
sort.Sort(sort.IntSlice(lst))
fmt.Println(lst)

This prints [1 2 3 4 5 8 9]. sort.Sort requires its argument to implement the sort.Interface:

type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

Convenience types like sort.IntSlice implement these methods for common cases. sort.Reverse works by wrapping such a value with an unexported struct that embeds the interface:

type reverse struct {
  sort.Interface
}

func (r reverse) Less(i, j int) bool {
  return r.Interface.Less(j, i)
}

The reverse type implements sort.Interface by embedding it (once initialized with a real value) and overrides only the Less method, delegating with inverted arguments. And sort.Reverse is just a constructor:

func Reverse(data sort.Interface) sort.Interface {
  return &reverse{data}
}

So the full invocation is:

sort.Sort(sort.Reverse(sort.IntSlice(lst)))
fmt.Println(lst)

This prints [9 8 5 4 3 2 1]. Note that sort.Reverse itself sorts nothing; it is a higher-order function that returns a wrapper adjusting the comparison order. Sorting happens later in sort.Sort.

Context Values via Embedded Interface

The context package uses the same trick in WithValue:

func WithValue(parent Context, key, val interface{}) Context

It returns a copy of the parent context that carries val for key. Ignoring error handling, the implementation is:

func WithValue(parent Context, key, val interface{}) Context {
  return &valueCtx{parent, key, val}
}

Where valueCtx is defined as:

type valueCtx struct {
  Context
  key, val interface{}
}

Again, a struct embeds the Context interface, inherits all its methods, and overrides only Value:

func (c *valueCtx) Value(key interface{}) interface{} {
  if c.key == key {
    return c.val
  }
  return c.Context.Value(key)
}

The remaining three methods of Context are delegated to the embedded parent automatically.

Restricting Capabilities with a Narrow Embedded Interface

A more advanced application appears in os.File.ReadFrom. The io.ReaderFrom interface is:

type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}

os.File implements it. The implementation first tries an OS-specific fast path:

func (f *File) ReadFrom(r io.Reader) (n int64, err error) {
  if err := f.checkValid("write"); err != nil {
    return 0, err
  }
  n, handled, e := f.readFrom(r)
  if !handled {
    return genericReadFrom(f, r)
  }
  return n, f.wrapErr("write", e)
}

On Linux, that path uses the copy_file_range syscall for kernel-level copying between two files. If the fast path is not available (handled is false), a generic fallback runs:

func genericReadFrom(f *File, r io.Reader) (int64, error) {
  return io.Copy(onlyWriter{f}, r)
}

The helper copies via io.Copy. The question is why the destination is wrapped in onlyWriter:

type onlyWriter struct {
  io.Writer
}

The wrapper defines no methods, so it intercepts nothing. The reason emerges from how io.Copy works: if its destination implements io.ReaderFrom, it calls ReadFrom on it. But that would call back into File.ReadFrom, causing infinite recursion. Wrapping f so that io.Copy sees only an io.Writer prevents that loop. The wrapper degrades the visible capability of *File to a plain writer.

This pattern recurs across the standard library. Some spots use an anonymous struct instead of a named type, for instance in the tar package:

io.Copy(struct{ io.Writer }{sw}, r)