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.