In programming, the principle “don’t repeat yourself”, DRY for short, recommends that we try to reuse the same implementation where possible. In this post, I will cover what it’s like to do it in golang through struct embedding and my personal thoughts on that pattern.

Golang struct embedding

One common way of code reuse is inheritance. In Java, we can do this by implementing a method on the parent class, then subclasses will inherit this implementation.

Golang does not support inheritance, but we can get pretty far with composition, in particular, with struct embedding. Composition is adding a field to a struct that references another struct. When the field is anonymous, it is called struct embedding. The effect of this is that fields and functions on the embedded struct is exposed to the outer struct as well.

type Child struct {
    parent Parent // explicit composition
}

type Child struct {
    Parent // implicit composition aka struct embedding
}

Note that even though the embedding is anonymous, it has to be explicitly specified when initializing the struct

func main() {
    Child{
        Parent: Parent {
            //...
        },
    }
}

Assuming the parent has the following receiver function, the child will also have access to the implementation.

type Parent struct {
    //... some fields
} 
func (p Parent) Do() {} // depends only on the fields of Parent

func usage(c Child) {
    c.Do() // here it looks as if the child has implemented Do().
}

This means if Parent satisfy some interface Doer, then the structs embedding Parent would also satisfy the interface Doer.

type Doer interface {
    Do()
}

var _ Doer = Parent{} // because we implemented it
var _ Doer = Child{} // this also compiles because it embeds Parent

Downsides: readability

This comes at a cost of readability if both Parent and Child have some field name conflicts and it’s not immediately obvious which struct it belongs to.

struct Parent {
    Name string
    Y int
}

struct Child {
    Parent
    Name string
}

func usage(c Child) {
    if c.name == "" { // Is this referring to the parent one or the child one?
        c.Do()
    }
    if c.Y > 10 { // This can also be confusing because Y is from the parent.
        c.Do()
    }
}

For this reason, I often prefer that the reference is explicit when referring to the parent field.

func usage(c Child) {
    if c.Parent.Y > 10 { // This is possible even though there is no named field "Parent" in Child.
        c.Do()
    }
}

Warning: Not true sub typing

The definition of subtype says that if A is a subtype of B, then wherever we expect a B, we can use a A.

Struct embedding does not satisfy this.

type Parent struct{}
type Child struct{
    Parent
}
func process(p Parent) {}
func main() {
    process(Child{}) // This does not compile.
}

Note: If Parent is an exported field, then we can still do process(Child{}.Parent) but this isn’t as “nice”.

Proxy pattern - explicit composition

As seen above, we trade off possible readability issues for the convenience of not redefining all the interface that the parent expose.

Suppose our goal is to make sure Child, which we conceptually think of as subtype of Parent, also satisfy an interface Doer, which Parent already implements, then we can implement the interface by calling Parent’s implementation

struct Child {
    Parent Parent // Prefer explicit reference.
    Name string
}

func (c Child) Do() {
    c.Parent.Do()
}

The downside here is that even though the implementation is trivial, it is still boilerplate that doesn’t say much. Code can get a bit repetitive when we start to proxy more than a handful of functions.

Consider the scenario where the parent now have 5 functions, and we have 5 children that are logical subtypes of the parent, using this pattern, we would have implemented 25 proxy functions that does not do anything other than to call the parent. Imaging having to add on another function to the parent that requires proxying, we now have to add 5 more boilerplate.