Anonymous fields in Go

Anonymous fields in Go structures allow us to shortcut dot notation as well as allow us to use compositions to add methods to a type.

Shortcutting the dot notation

For example if we have something like this

type A struct {
	X int
	Y int
}

type B struct {
	Z int
}

type C struct {
	AValue A
	BValue B
}

then to access the field X on AValue we’d write code like this

c := C {}
c.AValue.X = 1

Changing the C struct fields to anonymous (i.e. removing the field name) like this

type C struct {
	A
	B
}

allows us to reduce the use of the dot notation and access X on A as if it was a field within the C struct itself, i.e.

c := C {}
c.X = 1

Okay, but what if B struct now replaced the Z field name with X, i.e.

type A struct {
	X int
	Y int
}

type B struct {
	X int
}

We now have a situation where both A and B have the same field name, well we can still use anonymous fields, but we’re back to using more dot notation again to get around the obvious field name conflict, i.e.

c := C {}
c.A.X = 1
c.B.X = 2

Initializers do not support shortcutting

Unfortunately we not get the advantage of the anonymous fields with the initializer syntax, i.e. We must specify the A and B structs like this

c := C{ A : A{2, 4}, B : B { 12 }}

// or

c := C{ A : A{X : 2, Y : 4}, B : B { Z: 12 }}

// but this doesn't compile
c := C{ X : 2, Y : 4, Z : 12 }
// nor does this compile
c := C{ A.X : 2, A.Y = 3, B.Z : 12 }

Composition

More powerful than the synaptic sugar of embedded/anonymous fields, we can also use composition to increase the methods available on the struct C. For example let’s now add a method to the A type which allows us to move our X, Y point

func (a *A) Move(amount int) {
	a.X += amount
	a.Y += amount
}

Now we can call the method on the previously declared c variable like this

c.Move(10)

We can also create further methods via a composition pattern on type C if we want using empty types, let’s assume we have type D and extend the composition of type C

type D struct {}

type C struct {
	A
	B
	D
}

now we can add methods to D and ofcourse they’ll be available as part of C. So for example, maybe D acts as a receiver for methods that serialize data to JSON, now our C type will also appear to have those methods.