Structs

Chapter 13

Go has structs instead of classes as we see in other languages. Structs basically encompass some fields within.

package main
import (
"fmt"
)
type School struct {
Name string
Country string
NumOfStudents int
}
func main() {
school := School{"ABC Primary School", "Canada", 5000}
fmt.Println(school.Name)
}

Above, we have school as a struct that contains Name, Address and the NumOfStudents.

To access any of the fields within a struct, we use the . operator, e.g. school.Name.

We can initialise a struct in a number of ways.

func main() {
// initializing according to the order of fields without specifying field names
school := School{"ABC Primary School", "Canada", 5000}
fmt.Println(school.Name)
// initializing with field names and in random order
school2 := School{Name: "XYZ Primary School", NumOfStudents: 2000, Country: "America"}
fmt.Println(school2.NumOfStudents)
// initializing only some struct fields
school3 := School{Name:"Jingle Bells Secondary School"}
fmt.Println(school3.Name)
}

The above will output the following:

ABC Primary School
2000
Jingle Bells Secondary School

Note

Naming the fields within the struct starting with capital letters allows us to access them freely. Read on to find out how to restrict access.

Anonymous Struct Members#

In a way, inheritance can be done using anonymous struct members.

type Lab struct {
numOfEquipment int
}
type School struct {
Lab //anonymous field
numOfStudents int
}
func main() {
// initialise a school with 300 students and a lab with 10 equipment
school := School{Lab{10}, 300}
fmt.Println(school.numOfEquipment)
}

We are achieving inheritance via composition. This means school.numOfEquipment is a valid access even though School does not directly contain this field.

If two structs which you are "inserting" from has the same property name, we need to specify the "intermediate" struct type and then the property name.

type Lab struct {
numOfEquipment int
}
type SportsRoom struct {
numOfEquipment int
}
type School struct {
Lab
SportsRoom
}
func main() {
school := School{Lab{3}, SportsRoom{4} }
fmt.Println(school.Lab.numOfEquipment + school.SportsRoom.numOfEquipment)
}

Methods on Structs#

We can have pointer receivers for structs, i.e. the receiver type has a syntax *T, where T is the type of struct.

package main
import (
"fmt"
)
type Circle struct {
radius int
}
// pointer receiver method
func increaseRadiusByOne(c *Circle) {
c.radius += 1
}
// value receiver method
func increaseRadiusByTwo(c Circle) {
c.radius += 2
}
func main() {
c := Circle{2}
increaseRadiusByOne(&c) // passing the reference to c
fmt.Println(c.radius) // prints 3
increaseRadiusByTwo(c) // passing a copy of c
fmt.Println(c.radius) // still prints 3
}

Using a pointer receiver will modify the original struct as it receives a reference to the actual object itself. In the above example, increaseRadiusByOne(&c) passes the actual object c defined in the main method. Therefore, the actual radius is modified.

Using a value receiver receives a copy of the struct. Modifying the struct properties within this, there will not be any modifications on the original struct. In the above example, increaseRadiusByTwo(c) passes a copy of c and modifying the copy of c does not affect the actual c defined in the main method.