Embedding in Go: Part 1 – structs in structs

Go doesn’t support inheritance in the classical sense; instead, in encourages
composition as a way to extend the functionality of types. This is not a
notion peculiar to Go. Composition over inheritance is a known
principle of OOP and is featured in the very first chapter of the Design
Patterns
book.

Embedding is an important Go feature making composition more convenient and
useful. While Go strives to be simple, embedding is one place where the
essential complexity of the problem leaks somewhat. In this series of short
posts, I want to cover the different kinds of embedding Go supports, and provide
examples from real code (mostly the Go standard library).

There are three kinds of embedding in Go:

Structs in structs (this part)
Interfaces in interfaces (part 2)
Interfaces in structs (part 3)

Embedding structs in structs

We’ll start with a simple example demonstrating the embedding of a struct in
another struct:

type Base struct {
b int
}

type Container struct { // Container is the embedding struct
Base // Base is the embedded struct
c string
}

Instances of Container will now have the field b as well. In the
spec it’s called a promoted field. We can
access it just as we’d do for c:

co := Container{}
co.b = 1
co.c = “string”
fmt.Printf(“co -> {b: %v, c: %v}n”, co.b, co.c)

When using a struct literal, however, we have to initialize the embedded
struct as a whole, not its fields. Promoted fields cannot be used as field names
in composite literals of the struct:

co := Container{Base: Base{b: 10}, c: “foo”}
fmt.Printf(“co -> {b: %v, c: %v}n”, co.b, co.c)

Note that the access co.b is a syntactic convenience; we can also do it
more explicitly with co.Base.b.

Methods

Embedding structs also works well with methods. Suppose we have this method
available for Base:

func (base Base) Describe() string {
return fmt.Sprintf(“base %d belongs to us”, base.b)
}

We can now invoke it on instances of Container, as if it had this
method too:

fmt.Println(cc.Describe())

To understand the mechanics of this call better, it helps to visualize
Container having an explicit field of type Base and an explicit
Describe method that forwards the call:

type Container struct {
base Base
c string
}

func (cont Container) Describe() string {
return cont.base.Describe()
}

The effect of calling Describe on this alternative Container is similar
to our original one which uses an embedding.

This example also demonstrates an important subtlety in how methods on
embedded fields behave; when Base’s Describe is called, it’s passed
a Base receiver (the leftmost (…) in the method definition),
regardless of which embedding struct it’s called through. This is different
from inheritance in other languages like Python and C++, where inherited
methods get a reference to the subclass they are invoked through.
This is a key way in which embedding in Go is different from classical
inheritance.

Shadowing of embedded fields

What happens if the embedding struct has a field x and embeds a struct which
also has a field x? In this case, when accessing x through the embedding
struct, we get the embedding struct’s field; the embedded struct’s x is
shadowed.

Here’s an example demonstrating this:

type Base struct {
b int
tag string
}

func (base Base) DescribeTag() string {
return fmt.Sprintf(“Base tag is %s”, base.tag)
}

type Container struct {
Base
c string
tag string
}

func (co Container) DescribeTag() string {
return fmt.Sprintf(“Container tag is %s”, co.tag)
}

When used like this:

b := Base{b: 10, tag: “b’s tag”}
co := Container{Base: b, c: “foo”, tag: “co’s tag”}

fmt.Println(b.DescribeTag())
fmt.Println(co.DescribeTag())

This prints:

Base tag is b’s tag
Container tag is co’s tag

Note that when accessing co.tag, we get the tag field of
Container, not the one coming in through the shadowing of Base. We could
access the other one explicitly, though, with co.Base.tag.

Example: sync.Mutex

The following examples are all from the Go standard library.

A classical example of struct-in-struct embedding in Go is sync.Mutex.
Here’s lruSessionCache from crypto/tls/common.go:

type lruSessionCache struct {
sync.Mutex
m map[string]*list.Element
q *list.List
capacity int
}

Note the embedding of sync.Mutex; now if cache is an object of type
lruSessionCache, we can simply call cache.Lock() and cache.Unlock().
This is useful in some scenarios, but not always. If the locking is part of
the struct’s public API, embedding the mutex is convenient and removes the need
for explicit forwarding methods.

However, it could be that the lock is only used internally by the struct’s
methods and isn’t exposed to its users. In this case I wouldn’t embed the
sync.Mutex, but would rather make it an unexported field (like mu
sync.Mutex).

I’ve written some more on embedded mutexes and gotchas to look out for here.

Example: elf.FileHeader

The embedding of sync.Mutex is a good demonstration of struct-in-struct
embedding to gain new behavior. A different example involves an embedding for
data. In debug/elf/file.go we find
the structs that describe ELF files:

// A FileHeader represents an ELF file header.
type FileHeader struct {
Class Class
Data Data
Version Version
OSABI OSABI
ABIVersion uint8
ByteOrder binary.ByteOrder
Type Type
Machine Machine
Entry uint64
}

// A File represents an open ELF file.
type File struct {
FileHeader
Sections []*Section
Progs []*Prog
closer io.Closer
gnuNeed []verneed
gnuVersym []byte
}

The elf package developers could have just listed all the header fields
directly in File, but having it in a separate struct is a nice example of
self-documenting data partitioning. User code may want to initialize and
manipulate file headers separately from File, and the embedding design makes
this natural.

A similar example can be found in compress/gzip/gunzip.go, where gzip.Reader embeds
gzip.Header. This is a very nice example of embedding for data reuse because
gzip.Writer also embeds gzip.Header, so this helps avoid copy-pasta.

Example: bufio.ReadWriter

Since an embedding struct “inherits” (but not in the classical sense, as
described above) the methods of an embedded struct, embedding can be a useful
tool to implement interfaces.

Consider the bufio package, which has the type bufio.Reader. A pointer
to this type implements the io.Reader interface. The same applies to
*bufio.Writer, which implements io.Writer. How can we create a bufio
type that implements the io.ReadWriter interface?

Very easily with embedding:

type ReadWriter struct {
*Reader
*Writer
}

This type inherits the methods of *bufio.Reader and *bufio.Writer, and
thus implements io.ReadWriter. This is done without giving the fields
explicit names (which they don’t need) and without writing explicit forwarding
methods.

A slightly more involved example is timerCtx in the context package:

type timerCtx struct {
cancelCtx
timer *time.Timer

deadline time.Time
}

To implement the Context interface, timerCtx embeds cancelCtx, which
implements 3 of the 4 methods required (Done, Err and Value). It
then implements the fourth method – Deadline on its own.

Flatlogic Admin Templates banner

Leave a Reply

Your email address will not be published. Required fields are marked *