Skip to content
Ben Sooraj

Struct aggregation and embedding in golang

golang1 min read

A struct,

  • is a collection of fields of various types, incuding other structs!
  • can be used to create user-defined data types
  • can have methods defined on them
  • its fields and methods are accessed using the dot notation

For example, Point is a struct which has,

  • two fields X and Y of type float64
  • has two methods String and MakesNoSense
1// structs.go
2// Struct definition
3type Point struct {
4 X, Y float64
5}
6
7// String overrides how an entity of type `Point`
8// is printed (for example, to `stdout`)
9func (p Point) String() string {
10 return fmt.Sprintf("Point {%f, %f}", p.X, p.Y)
11}
12
13// MakesNoSense is just a dummy method
14func (p Point) MakesNoSense() {
15 fmt.Printf("Point {%f, %f} is just a point!\n", p.X, p.Y)
16 return
17}
18
19func main() {
20 var pt Point
21 pt.X = 1.123 //
22 pt.Y = 2.123 //
23
24 fmt.Println(pt)
25 pt.MakesNoSense()
26}

results in,

1$ go run structs.go
2Point {1.123000, 2.123000}
3Point {1.123000, 2.123000} is just a point!

Nested Structs

Yes, you can define one struct inside another. There are two ways to go about it: struct aggregation and struct embedding.

Struct Aggregation

This approach implies a has-a relationship. For example, a line has two points (start and end) and can be declared as follows:

1// Line struct has two fields of type Point
2type Line struct {
3 Start, End Point
4}
5
6// Distance methods calculates the euclidean distance
7// between the two Points
8func (l Line) Distance() float64 {
9 xDiff := math.Abs(l.Start.X - l.End.X)
10 yDiff := math.Abs(l.Start.Y - l.End.Y)
11
12 return math.Sqrt(math.Pow(xDiff, 2) + math.Pow(yDiff, 2))
13}
14
15// Usage
16func main() {
17 l := Line{
18 Start: Point{
19 X: 2.304,
20 Y: 4.504,
21 },
22 End: Point{
23 X: 30.607,
24 Y: 44.104,
25 },
26 }
27 fmt.Printf("Distance from %v to %v is %f units\n", l.Start, l.End, l.Distance())
28}
29// Distance from Point {2.304000, 4.504000} to Point {30.607000, 44.104000} is 48.674632 units

Let's redefine Line struct slightly differently using inline structs,

1type Line struct {
2 Start, End struct {
3 X float64
4 Y float64
5 }
6}
7
8// Usage
9func main() {
10 l := Line{
11 Start: struct {
12 X float64
13 Y float64
14 }{
15 X: 3.123,
16 Y: 8.123,
17 },
18 End: struct {
19 X float64
20 Y float64
21 }{
22 X: 4.123,
23 Y: 7.123,
24 },
25 }
26}

This approach requires you to rely on anonymous structs during initialization.

Struct Embedding

This approach implies an is-a relationship. For example, a rectangle is a polygon and can be decalred as shown below:

1// embed.go
2// Polygon has just two fields for the sake of simplicity
3type Polygon struct {
4 Width, Height int
5}
6
7// Set width and height of the polygon
8func (p *Polygon) Set(w, h int) {
9 p.Width = w
10 p.Height = h
11}
12
13// Rectangle is a polygon, with one extra field 'color'
14type Rectangle struct {
15 color string
16 Polygon // Notice the embedding?
17}
18
19// Area method can access the fields Width and Height even though
20// they are not directly defined within the Rectangle struct
21func (r *Rectangle) Area() float64 {
22 return float64(r.Width * r.Height)
23}
24
25func main() {
26 var rect Rectangle
27 rect.Set(10, 20) // direct
28 rect.color = "Blue"
29 fmt.Printf("Rectangle: %+v\n", rect)
30 fmt.Printf("Rectangle Width: %+v\n", rect.Width) // direct
31 fmt.Printf("Rectangle Height: %+v\n", rect.Height) // direct
32 fmt.Printf("Area of the rectangle is: %+v\n", rect.Area())
33
34 rect.Polygon.Set(100, 200) // indirect
35 fmt.Printf("Rectangle: %+v\n", rect)
36}

results in,

1$ go run embed.go
2Rectangle: {color:Blue Polygon:{Width:10 Height:20}}
3Rectangle Width: 10
4Rectangle Height: 20
5Area of the rectangle is: 200
6Rectangle: {color:Blue Polygon:{Width:100 Height:200}}

You can see that rect can access:

  1. Rectangle struct's fields and methods
  2. Polygon struct's fields and methods - both directly and indirectly

I am not sure if that's the correct way to put it, but these are my observations.


Note: This article is not an in-depth tutorial or treatment of Golang's syntax, semantics, design or implementation, but a journal of my learnings.