9. Structs

A struct is a collection of fields and is defined with the type and struct keywords.

An example:

1 type myStructName struct {
2         x int
3         y string
4 }

The type keyword introduces a new type. It is followed by the name of the type (myStructName), the keyword struct to indicate that we are defining a struct type and a list of fields inside of curly braces. Each field has a name and a type. Like with functions we can collapse fields that have the same type:

1 type Circle struct {
2     x, y, r float64
3 }

Struct fields are accessed using a dot. Here’s an example:

Program: struct1.go


 1 package main
 2 
 3 import "fmt"
 4 
 5 // this struct is visible only in this package
 6 // as it starts with a small letter
 7 type myStructName struct {
 8         // variables starts with small letter,
 9         // so NOT visible outside package
10         x int
11         y string
12 }
13 
14 func main() {
15         msn := myStructName{1, "talim"}
16         msn.x = 4
17         fmt.Println(msn.x)
18 }

The output is is 4.

Let us define a Square struct as follows:

Program: struct2.go


 1 package main
 2 
 3 import "fmt"
 4 
 5 // this struct is visible outside this package
 6 // as it starts with a capital letter
 7 type Square struct {
 8         // variable X starts with a capital letter,
 9         // so visible outside package
10         X    int
11         name string
12 }
13 
14 func main() {
15         sqr := Square{}
16         fmt.Println("Default square is: ", sqr)
17 }

When you run the example, the output is Default square is: {0 }

This means that in a struct the values of the variables by default for an int will be 0; a string will be empty, etc.

Now let us look at different ways of initializing a struct and setting values for variables within it.

Program: struct3.go


 1 package main
 2 
 3 import "fmt"
 4 
 5 type Square struct {
 6         X    int
 7         name string
 8 }
 9 
10 func main() {
11         //initialize values in order they are defined in struct
12         sqr1 := Square{10, "Go_Sqr1"}
13         fmt.Println("Square sqr1 is    : ", sqr1)
14 
15         //initialize values by variable name in any order
16         sqr2 := Square{name: "Go_Sqr2", X: 15}
17         fmt.Println("Square sqr2 is    : ", sqr2)
18 
19         //get pointer to an instance with new keyword
20         ps := new(Square)
21 
22         //set value using . notation by dereferencing pointer
23         (*ps).X = 30
24 
25         //set value using . same as above
26         ps.name = "Go_Sqr3"
27 
28         fmt.Println("Square ps is      : ", *ps)
29         
30         ps1 := &sqr1
31         ps1.X = 22
32         fmt.Println("The new value of X: ", ps1.X)
33         fmt.Println("Square sqr1 is    : ", sqr1)
34 }

The output is:

Square sqr1 is    :  {10 Go_Sqr1}
Square sqr2 is    :  {15 Go_Sqr2}
Square ps is      :  {30 Go_Sqr3}
The new value of X:  22
Square sqr1 is    :  {22 Go_Sqr1}

Note: As seen above, you can get a pointer to a newly created struct instance using the new keyword. A pointer thus obtained, can be used with or without using the * operator to get variables within it.

Thus new(T) returns a pointer to a newly allocated zero value of type T. This is important to remember.

This means a user of the data structure can create one with new and get right to work.

Another example:

Program: struct_ptr.go


 1 package main
 2 
 3 import "fmt"
 4 
 5 type Person struct {
 6 	name string
 7 	age  int
 8 }
 9 
10 func main() {
11 	fmt.Println(Person{"Kiran", 30})
12 	fmt.Println(Person{name: "Satish", age: 60})
13 
14 	s := Person{"Victor", 45}
15 	fmt.Println(s)
16 
17 	r := &Person{"Harry", 30} // Pointer
18 	fmt.Println(r)
19 	r.age = 33      // Changing the values
20 	fmt.Println(*r) // Pointer de-referencing
21 
22 	// Another example of Pointers
23 	p := &Person{}
24 	p.name = "Hulk"
25 	p.age = 10
26 	fmt.Println(*p)
27 }

The output is:

{Kiran 30}
{Satish 60}
{Victor 45}
&{Harry 30}
{Harry 33}
{Hulk 10}

An anonymous struct field

Program: struct4.go


 1 package main
 2 
 3 import "fmt"
 4 
 5 type A struct {
 6         ax, ay int
 7 }
 8 type B struct {
 9         A
10         bx, by float64
11 }
12 
13 func main() {
14         b := B{A{1, 2}, 3.0, 4.0}
15         fmt.Println(b.ax, b.ay, b.bx, b.by)
16         fmt.Println(b.A)        
17 }

The output is:

1 2 3 4
{1 2}

Anonymous fields of any type

Any named type, or pointer to one, may be used in an anonymous field and it may appear at any location in the struct.

Program: struct5.go


 1 package main
 2 
 3 import "fmt"
 4 
 5 type C struct {
 6         x float64
 7         int
 8         string
 9 }
10 
11 func main() {
12         c := C{3.5, 7, "hello"}
13         fmt.Println(c.x, c.int, c.string)
14 }

The output is:

3.5 7 hello

Now let us understand how to define a function on a struct.

A normal function that we name myFunc that takes no parameters and returns an int would be defined similar to the code snippet shown below:

1 func myFunc() int {
2         //code
3 }

A function like myFunc that takes no parameters and returns an int, but which is associated with a type which we call as myType would be defined similar to the code snippet shown below:

1 type myType struct{}
2 
3 func (f myType) myFunc() int {
4         //code
5 }

In between the keyword func and the name of the function (myFunc()) we have added a “receiver”. The receiver is like a parameter – it has a name and a type. In Go, a function which takes a receiver is usually called a method.

Let’s extend our earlier Square struct to add an Area function. This time we will define that the Area function works explicitly with the Square type:

Program: struct6.go


 1 package main
 2 
 3 import "fmt"
 4 
 5 type Square struct {
 6         X    int
 7         name string
 8 }
 9 
10 func (s Square) Area() int {
11         return s.X * s.X
12 }
13 
14 func main() {
15         r1 := Square{5, "my_sqr"}
16         fmt.Println("Square is: ", r1)
17         fmt.Println("Square area is: ", r1.Area())
18 }

The output is:

Square is:  {5 my_sqr}
Square area is:  25

Exercise 14

Create a simple stack Go program which can hold a fixed number of ints. It does not have to grow beyond this limit. Define push() – put something on the stack – and pop() – retrieve something from the stack – functions. The stack should be a LIFO (last in, first out) stack.

9.1 Solutions

Exercise 14 Solution1.

  1. https://play.golang.org/p/ya7p5kOvom