Structures are one of the ways to organize data into structured data structure. This is also the data structure which helps avoid having objects and classes because we can define the blueprint of data using structs.

Overview

Structure is a user-defined data type which groups several data elements for related entity. This allows to store multiple values of different data types under single variable name. They allow you to create complex data types in Go. For example, a person may have name, address, citizenship, security number, etc. So, we can create a struct for Person and it can include all these information for a person under single variable.

Define and Initialize Structure

To initialize a structure we can use below syntax.

1type <Name> struct {
2    field1 <data_type>
3    field2 <data_type>
4    ... ...
5}

Example:

 1package main
 2
 3import "fmt"
 4
 5// define struct for Point
 6type Point struct {
 7	x int
 8	y int
 9}
10
11func main() {
12	var pt Point // allocates memory and zero value to fields
13	fmt.Println(pt)
14	fmt.Printf("%+v\n", pt)
15	fmt.Printf("%v\n", pt)
16}

By default, if we do not assign a value to fields of a struct, they will have zero value for their respective data type.

Output:

{0 0}
{x:0 y:0}
{0 0}

Struct assigns contiguous blocks of memory for each of its members. So, if we have a struct with 2 fields of int16, it will assign 4 bytes plus a padding byte.

new Keyword

We can also use new keyword to create a variable of struct type. This allocates memory for each of the fields of the struct, assigns them zero values and returns the pointer to the struct.

 1package main
 2
 3import "fmt"
 4
 5type Point struct {
 6	x int
 7	y int
 8}
 9
10func main() {
11	pt2 := new(Point)
12	fmt.Printf("%+v\n", pt2)
13	fmt.Printf("%+v\n", *pt2)
14}

Output:

&{x:0 y:0}
{x:0 y:0}

The use of new keyword is less common because usually we want to provide initial values to fields. We can initialize structure while defining a structure.

Example:

 1package main
 2
 3import "fmt"
 4
 5type Point struct {
 6	x int
 7	y int
 8}
 9
10func main() {
11	pt := Point{
12		x: 1,
13		y: 2,
14	}
15	fmt.Printf("%+v\n", pt)
16}

Output:

{x:1 y:2}

It is also possible to omit the field name and the values will be assigned in the sequence of field names. This is less common due to code readability. So, we can also create Point struct using below syntax.

1pt := Point{1, 2}
2fmt.Printf("%+v", pt) // {x:1 y:2}

The problem with these types of assignment is if the types do not match the declaration, it will give compile error. For example, in below code, we have Student struct, but the type of id do not match the declaration of the struct while assigning student2 variable. So, it gives compile error.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	student1 := Student{1, "Andrew"}
 7	fmt.Printf("%+v\n", student1)
 8	student2 := Student("Anita", 2) // gives error
 9	fmt.Printf("%+v\n", student2) 
10}
11
12type Student struct {
13	id   int
14	name string
15}

Accessing Struct Fields

We can access individual fields of the struct using dot operator (.). We can also modify fields of a structure.

Example:

 1package main
 2
 3import "fmt"
 4
 5type Point struct {
 6	x int
 7	y int
 8}
 9
10func main() {
11	pt := Point{
12		x: 1,
13		y: 2,
14	}
15	fmt.Printf("%d, %d\n", pt.x, pt.y)
16	pt.y = 1 // modify field of a struct
17	fmt.Printf("%d, %d\n", pt.x, pt.y)
18}

Output:

1, 2
1, 1

Structures as Function Argument

Passing structures as parameters is similar to using any other variable type. In Go, there is math package which contains math related functions. In below example, we use Pow function to calculate power and Sqrt function to calculate square-root of a number.

Example:

 1package main
 2
 3import (
 4	"fmt"
 5	"math"
 6)
 7
 8type Point struct {
 9	x int
10	y int
11}
12
13func main() {
14	var pt Point
15	pt3 := Point{
16		x: 1,
17		y: 2,
18	}
19	fmt.Printf("%+v\n", pt)
20	fmt.Printf("%+v\n", pt3)
21	fmt.Printf("%f\n", calcDistance(pt, pt3))
22}
23
24func calcDistance(p1 Point, p2 Point) float64 {
25	distance := math.Sqrt(math.Pow(float64(p1.x-p2.x), 2) + math.Pow(float64(p1.y-p2.y), 2))
26	return distance
27}

Output:

{x:0 y:0}
{x:1 y:2}
2.236068

We can also pass by reference.

Example:

 1package main
 2
 3import "fmt"
 4
 5type Person struct {
 6	firstName string
 7	lastName  string
 8	fullName  string
 9}
10
11func main() {
12	p := Person{
13		firstName: "John",
14		lastName:  "Doe",
15		fullName:  "",
16	}
17	fmt.Printf("%+v\n", p)
18	populateFullName(&p)
19	fmt.Printf("%+v\n", p)
20}
21
22func populateFullName(p *Person) {
23	(*p).fullName = p.firstName + " " + p.lastName
24}

In this case, we are passing a Person struct by reference as we can see in the populateFullName function definition. It has a parameter which is of type *Person.

Output:

{firstName:John lastName:Doe fullName:}
{firstName:John lastName:Doe fullName:John Doe}

Comparing Structures

We can compare structs using equality operator. This checks the fields for comparison. If all fields of a structure match, they are considered equal.

Example:

 1package main
 2
 3import "fmt"
 4
 5type Point struct {
 6	x int
 7	y int
 8}
 9
10func main() {
11	p1 := Point{1, 2}
12	p2 := Point{1, 1}
13	p3 := Point{1, 2}
14	fmt.Println(p1 == p2) // type and fields should be same
15	fmt.Println(p1 == p3)
16}

Output:

false
true

Define Methods for Struct

In Go, we have methods which are defined differently. A method is something that augments a function with an extra parameter that specifies the receiver. This argument is added right after func keyword and it accepts only one argument. This is called a receiver. A method is a function that has a defined receiver. This ensures that any instance of Person struct will have name method available with them. This method is called with a dot operator after the struct variable. These methods are very similar to methods used in object-oriented programming. Go does not have classes like other OOP languages.

Example:

 1package main
 2
 3import "fmt"
 4
 5type Person struct {
 6	firstName string
 7	lastName  string
 8	fullName  string
 9}
10
11func main() {
12	p := Person{
13		firstName: "John",
14		lastName:  "Doe",
15		fullName:  "",
16	}
17	fmt.Printf("%+v\n", p)
18	p.name()
19	fmt.Printf("%+v\n", p)
20	fmt.Printf("%v\n", p.name())
21}
22
23func (p Person) name() string {
24	p.fullName = p.firstName + " " + p.lastName
25	return p.fullName
26}

Here, the method name() has a receiver type of Person in the method declaration. Within the method, you can have access to fields of a struct. In this method, we are using firstName and lastName fields from the struct. The struct variable is passed by value. So, it does not modify the original fields of the structure.

Output:

{firstName:John lastName:Doe fullName:}
{firstName:John lastName:Doe fullName:}
John Doe

Method Sets

Method sets are a set of methods available to a data type and are used to encapsulate functionality to specific data type. Here, we defined two methods calculateBonus and calculateGrossSalary on structure of type Employee.

Example:

 1package main
 2
 3import "fmt"
 4
 5type Employee struct {
 6	name       string
 7	role       string
 8	department string
 9	salary     float64
10}
11
12func main() {
13	e1 := Employee{
14		name: "John Doe", role: "Engineer", department: "Marketing", salary: 10000.0,
15	}
16	e2 := Employee{
17		name: "Jane Doe", role: "Manager", department: "Technology", salary: 100000.0,
18	}
19	fmt.Printf("Salary = %.2f, Bonus = %.2f, Gross salary = %.2f\n", e1.salary, e1.calculateBonus(), e1.calculateGrossSalary())
20	fmt.Printf("Salary = %.2f, Bonus = %.2f, Gross salary = %.2f\n", e2.salary, e2.calculateBonus(), e2.calculateGrossSalary())
21}
22
23func (e *Employee) calculateBonus() float64 {
24	return 0.10 * e.salary
25}
26
27func (e *Employee) calculateGrossSalary() float64 {
28	if e.department == "Sales" || e.department == "Marketing" {
29		return e.salary + e.calculateBonus() + 0.10*e.salary
30	} else {
31		return e.salary + e.calculateBonus()
32	}
33}

Output:

Salary = 10000.00, Bonus = 1000.00, Gross salary = 12000.00
Salary = 100000.00, Bonus = 10000.00, Gross salary = 110000.00