Chapter 1: Structs

Everything needs communication. Animals and humans communicate using physical gestures and sounds. Most insects have a special antenna in them that they use to communicate to other insects or emits sounds or scents to exchange information with other animals. Plants also have a special way of communicating with other plants through electric signaling or using other hosts to send their messages.

From gigantic animals to small microscopic bacterias, viruses to cells, all have a special way to exchange messages. They all have a way to coordinate information with each other.

Computers and software also require much communication.

Inside the computer hardware, electrical signals are sent to each component where they are task to perform a particular function. And computers themselves communicate to other computers via wired, wireless, and The Internet.

It is hard to imagine a world without communication!

In programming, there are also various levels of ways to communicate your code. In this chapter, we will discuss ways we can communicate to individual parts of code.

If you need to pass, exchange, and coordinate values bound to specific parts of the code, we can use a variable.

Variables can either be a local variable that you can only access inside the function or a global variable that you can globally access throughout your code.

For our first program example, I assume that you have prior Ruby knowledge, so that we will do a simple recap in Ruby. We will create a function that takes a single word input to dissect it in Go. This code will output the word “Hello World.”

Here, we have a local variable in Ruby. In this example, we declared a local variable hello inside a function say_hello.

1 def say_hello(message)
2   hello = "Hello"
3   puts hello + message
4 end
5 
6 say_hello("World")

And when we run the Ruby code, it will output “Hello World.”

1 $ ruby say_hello.rb
2 
3 Hello World

However, if we want to change the hello variable to store a different message, we cannot directly change it from outside the function.

1 def say_hello(message)
2   hello = "Hello"
3   puts hello + message
4 end
5 
6 hello = "Hi"
7 say_hello("World")

So even though we change the hello variable, this will still output a “Hello World.”

1 $ ruby say_hello.rb
2 
3 Hello World

Questions

Instance Variables

How do we change the information from inside different functions?

What we need is an instance variable. An instance variable is a variable that you can access via a single reference. In Ruby, the instance variable starts with an @ sign, followed by the variable name.

You can treat an instance variable like any normal variable, only that you can pass or modify information around it to other functions regardless of its location.

So back to our example, we changed the local variable hello to @hello, and then we can change the message of the variable.

1 def say_hello(message)
2   @hello = "Hello"
3   puts @hello + message
4 end
5 
6 @hello = "Hi"
7 say_hello("World")

This will output a “Hi World” message.

1 $ ruby say_hello.rb
2 
3 Hi World

This is very convenient because we can pass values to other functions without initializing any object.

Questions

Struct

struct is a type that you can declaratively group and define data fields accessible via a single reference. struct also provides a way to pass a value from within functions.

In Go, we can use structs to store a value from our functions. Here is an example of how to create a struct.

1 type Employee struct {
2   FirstName string
3   LastName  string
4 }

Using a struct, you can set a value and get a value from within your functions. This capability to exchange data throughout your code from within functions is useful for exchanging and coordinating data onto other parts of your code.

In Ruby, a struct can be equivalent to an instance variable.

In the following example, let’s discuss this in detail, starting with a Ruby implementation on how you pass values from within different parts of the code. We can proceed with a Go example on how to pass values from one function to another function.

In the following Ruby example, a class Dog has an initializer that accepts a parameter breed and sets that parameter to an instance variable @breed, which provides data access from within the class.

In this case, a public method kind directly returns the value passed to the initializer.

Ruby Instance Variables
 1 class Dog
 2   def initialize(breed)
 3     @breed = breed
 4   end
 5 
 6   def kind
 7     @breed
 8   end
 9 end
10 
11 dog = Dog.new('Rottweiler')
12 dog.kind
1 $ ruby dog.rb
2 
3 Rottweiler

The use of instance variables in the Ruby example can be rewritten in Go, using struct.

In the next example, a dog struct defines a property breed with a value type string and inside main(). The struct dog initializes to a variable kind with its property breed filled in.

Go Private Struct
 1 package main
 2 
 3 import "fmt"
 4 
 5 type dog struct {
 6   breed string
 7 }
 8 
 9 func main() {
10   kind := dog{
11     breed: "Rottweiler",
12   }
13 
14   fmt.Println(kind.breed)
15 }
1 $ go run dog.go
2 
3 Rottweiler

Since struct can group similar data field types, you can extend this struct by adding multiple fields.

1 type dog struct {
2   name  string
3   breed string
4   age   int
5 }

Then we can access multiple fields this way.

Go Private Struct
 1 package main
 2 
 3 import "fmt"
 4 
 5 type dog struct {
 6   name  string
 7   breed string
 8   age   int
 9 }
10 
11 func main() {
12   pet := dog{
13     name: "Maximus",
14     breed: "Rottweiler",
15     age: 5,
16   }
17 
18   fmt.Printf("%+v", pet)
19 }
1 $ go run dog.go
2 
3 {name:Maximus breed:Rottweiler age:5}

Questions

Public Structs

Take note that the struct dog is only available internally from within the package.

To make this public, the struct type alias name needs capitalization, in this case to Dog.

The same naming mechanics can also apply to the struct field names, functions, and variables. Provide an option to make its resource publicly available for other packages by capitally them.

Go Public Struct
 1 package main
 2 
 3 import "fmt"
 4 
 5 type Dog struct {
 6   Breed string
 7 }
 8 
 9 func main() {
10   kind := Dog{
11     Breed: "Rottweiler",
12   }
13 
14   fmt.Println(kind.Breed)
15 }
1 $ go run dog.go
2 
3 Rottweiler

In most cases, the equivalent functionality of an instance variables in Go would be using struct. And you can extend struct by attaching it to a function, forming methods to a struct that you can perform mutations of existing values.

Questions

Attaching a Struct to a Function

Supposed we like to perform a method on a declared variable.

In Ruby, this is an OOP call since Ruby can create methods in a class. This allows you to call those methods when you initialize the class using dot notation.

However, Go’s first-class citizens are functions and not an OOP language by design. Since there’s no concept of class, how do we attach a method from within a declared variable in a similar manner in Ruby?

In Go, we can associate a struct to a function by specifying the type of that function as one of the declared structs. This will provide the capability to create methods from within functions.

In the following example in Ruby, an instance variable produce
declares as a receiver. There are also three methods to operate on the variable: ` add_item, change_item and items`.

In the add_item method, using a double-splat parameter operator, we generate a hash from the method appending the values to produce.

The change_item method deletes the entry from the
hash and then appends to produce as a new entry.

Operating On A Reciever Instance Variable in Ruby
 1 class Basket
 2   def initialize
 3     @produce = []
 4   end
 5 
 6   def add_item(**entry)
 7     # Raise an error if name, flavour and
 8     # kind keys are not passed
 9     items = %w[name flavour kind]
10 
11     unless items.any? { |key| entry.key? key.to_sym }
12       raise "Usage: add_item(name:    '..',
13                              flavour: '..',
14                              kind:    '..')"
15     end
16 
17     @produce << entry
18 
19     puts "Entry #{entry[:name]} created!"
20   end
21 
22   def change_item(name, entry)
23     # Delete existing record
24     item = @produce
25            .delete_if { |h| h[:name] == name }
26            .first
27            .dup
28 
29     item = entry
30 
31     # Add it to the instance variable
32     @produce << item
33     @produce.uniq!
34 
35     puts "Item #{name} changed!"
36   end
37 
38   def items
39     puts "There are #{@produce.count} number of items in
40 the basket"
41 
42     @produce.each do |entry|
43       item(entry)
44     end
45   end
46 
47   private
48 
49   def item(entry)
50     puts "Name: #{entry[:name]}"
51     puts "Flavour: #{entry[:flavour]}"
52     puts "Kind: #{entry[:kind]}"
53   end
54 end
55 
56 basket = Basket.new
57 
58 basket.add_item(
59   name: 'apple',
60   flavour: "It's a little sour and bitter, but mostly
61 sweet, not at all salty, very juicy in general",
62   kind: 'fruit'
63 )
64 
65 basket.add_item(
66   name: 'carrot',
67   flavour: '',
68   kind: 'veggies'
69 )
70 
71 basket.change_item(
72   'carrot',
73   name: 'cucumber',
74   flavour: 'Slightly bitter with a mild melon aroma,
75 and planty flavor.',
76   kind: 'veggies'
77 )
78 
79 basket.items
 1 $ ruby basket.rb
 2 
 3 Entry apple created!
 4 Entry carrot created!
 5 Item carrot changed!
 6 
 7 There is 2 number of items in the basket.
 8 
 9 Name: apple
10 Flavor: It's a little sour and bitter, but slightly sweet,
11 not at all salty, juicy in general
12 Kind: fruit
13 
14 Name: cucumber
15 Flavor: Slightly bitter with a mild melon aroma, and
16 leafy flavor.
17 Kind: veggies

To rewrite Go, a struct must represent the produce with properties name, flavor and kind.

Another struct named basket will be an array of produce since basket is an array of a struct that will contain the produce struct.

We will define a function and attach it to basket to interact with produce. We will call the defined basket functions as add_item, change_item, and items.

Operating on a Receiver Struct in Go
 1 package main
 2 
 3 import "fmt"
 4 
 5 type basket []produce
 6 
 7 type produce struct {
 8 	name    string
 9 	flavour string
10 	kind    string
11 }
12 
13 func (p *basket) add_item(entry produce) {
14 	*p = append(*p, entry)
15 
16 	fmt.Printf("Entry %s created!\n", entry.name)
17 }
18 
19 func (p basket) change_item(name string, entry produce) {
20 	for key, val := range p {
21 		if val.name == name {
22 			p[key] = entry
23 		}
24 	}
25 
26 	fmt.Printf("Item %s changed!\n", name)
27 }
28 
29 func (p basket) items() {
30 	fmt.Printf("There are %d number of items in the basket\n",
31 		len(p))
32 
33 	for _, val := range p {
34 		fmt.Printf(`Name: %s\n
35                 Flavour: %s\n
36                 Kind: %s\n`,
37 			val.name,
38 			val.flavour,
39 			val.kind)
40 		fmt.Println("")
41 	}
42 }
43 
44 func main() {
45 	basket := new(basket)
46 
47 	basket.add_item(
48 		produce{
49 			name: "apple",
50 			flavour: `It's a little sour and bitter, but mostly
51 sweet, not at all salty, very juicy in general.`,
52 			kind: "fruit",
53 		},
54 	)
55 
56 	basket.add_item(
57 		produce{
58 			name:    "carrot",
59 			flavour: "",
60 			kind:    "veggies",
61 		},
62 	)
63 
64 	basket.change_item("carrot",
65 		produce{
66 			name: "cucumber",
67 			flavour: `Slightly bitter with a mild melon aroma
68 and planty flavor.`,
69 			kind: "veggies",
70 		},
71 	)
72 
73 	basket.items()
74 }
 1 $ go run basket.go
 2 
 3 Entry apple created!
 4 Entry carrot created!
 5 Item carrot changed!
 6 
 7 There is 2 number of items in the basket.
 8 
 9 Name: apple
10 Flavor: It's a little sour and bitter, but slightly sweet,
11 not at all salty, juicy in general.
12 Kind: fruit
13 
14 Name: cucumber
15 Flavor: Slightly bitter with a mild melon aroma and leafy
16 flavor.
17 Kind: veggies

Questions

Pass-by-value and Pass-by-reference

In our above basket example, notice two types of functions attached to basket?

It might be a new concept coming from Ruby, but when dealing with pointers and references, there is two semantics of passing a value to a variable.

Let’s discuss them in detail.

Those are the two types of parameter passing in Go, namely pointer receiver and value receiver.

- Value receivers are pass-by-value, which means that the variable will use an actual value or a resulting value.
- Pointer receivers are pass-by-reference, which takes in the memory address pointing to the value and passes it to the variable.

Ruby by default is pass-by-value, and in Go, we can use those two types of the semantics of passing and using a variable value. We will discuss them further in detail.

Questions

Pointer Receiver

Pointer receiver is pass-by-reference, which means that we pass the reference to the memory address of the resulting value to a variable.

Pointer receivers will create a copy to a new variable referencing the value’s memory address per each assignment. When you modify the parameter values, it will be a modification referencing the memory address of the original variable’s value.

To explain this further, let’s go back to our previous example. In the previous example, add_item expects a produce argument, and the receiver of this function is basket.

Notice that there is an asterisk before the receiver type, p *basket, which means that the receiver basket can be accessed and mutated directly within the function.

Pointer receivers are both setters and getters in Go, in the sense that the receiver basket is settable and gettable directly.

Pointer receivers can be an ideal use case when you are looking for an attr_accessor analog in Go, compared to the same behavior keen in Ruby. Here’s the general form of constructing a pointer receiver.

Pointer Receiver in Go
 1 func (receiverName *receiverType)
 2      funcName(paramName paramType) {
 3   // we can directly set the value of the
 4   // receiver type (setter)
 5   *receiverName = paramName
 6 
 7   // we can also access the value of the
 8   // receiver type (getter)
 9   fmt.Println(receiverName)
10 }

Questions

Value Receiver

Value receiver is pass-by-value, which means that we pass the actual value or a resulting value to a variable. Value receivers will create a new independent variable copying the original value per each assignment.

Two functions in our previous example utilize a value receiver function: those methods where the receiver type does not start with an asterisk, namely, the change_item and items functions which are also a method for the basket struct.

You might ask: Why does change_item mutate the value of the basket struct? Does this mean that the value receiver is also a setter? And the same with the pointer receiver?

You can modify the parameter values, but changes are not forwarded to the original variable. It is possible to modify existing variables with records, and value receivers can be setters of an initialized variable. However, any modifications to the variable will not reflect on new struct records. We must create the struct first and initialize it separately before passing values to the new structure record.

Value receivers are ideal for concurrent applications because they do not modify the original reference but create a new reference copy, making value receivers thread-safe. To explain this much further, let’s go back to our example above.

In our pointer receiver example, we used append to append a produce in a newly initialized basket array. The change_item mutates an already initialized basket variable but cannot mutate the struct basket directly. Here’s the general form of creating a value receiver.

Value Receiver in Go
 1 func (receiverName receiverType)
 2      funcName(paramName paramType) {
 3   // we can set the value of an
 4   // _already initialised_ receiver type
 5   // but we cannot modify the receiver
 6   // type directly
 7   receiverName = parameterName
 8 
 9   // we can access the value of the
10   // _already initialised_
11   // receiver type (getters)
12   fmt.Println(receiverName)
13 }

Questions

Decouple and Reuse Structs through Inheritance

One of the nicest features of struct is decoupling the data structures into smaller chunks inheriting a common structure, allowing specific implementations for each data structure.

Modularizing your data structure is a good practice by separately grouping your data struct. When decoupling your structs into smaller chunks, you can maximize the reusability of your struct because it
allows you to interchange your struct into varied use cases, focusing on the content of each structure.

In the long run, your code will be easy to maintain due to the
modularized organization of your structures, adding the overall
the simplicity of using and maintenance of your code.

In the following example, we create a common Animal struct and a Dog struct. Specifying the name of the struct on top of the field inherits the struct and its field properties. In this case, the Dog struct inherits the Animal struct and its fields. Adding an asterisk before the name makes the inheritance a pointer receiver, which allows modifications to the struct field values.

 1 type Animal struct {
 2   Kind    string
 3   Habitat string
 4   Origin  string
 5   Diet    string
 6 }
 7 
 8 type Dog struct {
 9   *Animal
10   Name  string
11   Breed string
12 }

And we can also have the ability to inherit multiple structures. Here’s the complete example where the Dog and Cat structs are inheriting both Animal and Owner, and in the main(), we can add the values for each field.

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Animal struct {
 6   Kind string
 7   Diet string
 8 }
 9 
10 type Owner struct {
11   Name    string
12   Country string
13 }
14 
15 type Dog struct {
16   *Animal
17   *Owner
18   Name  string
19   Breed string
20 }
21 
22 type Cat struct {
23   *Animal
24   *Owner
25   Name  string
26   Breed string
27 }
28 
29 func main() {
30   var dog Dog
31 
32   dog.Name = "Maximus"
33 
34   dog.Animal = &Animal{
35     Kind: "Dog",
36     Diet: "Omnivorous",
37   }
38 
39   dog.Breed = "Pitbull"
40 
41   dog.Owner = &Owner{
42     Name:    "John",
43     Country: "USA",
44   }
45 
46   fmt.Printf("Name of %s is %s\n", dog.Animal.Kind,
47 dog.Name)
48   fmt.Printf("%s is a %s with an %s diet\n", dog.Name,
49 dog.Breed, dog.Animal.Diet)
50   fmt.Printf("%s is owned by %s who lives in %s\n",
51 dog.Name, dog.Owner.Name, dog.Owner.Country)
52 }
1 $ go run animal.go
2 
3 The name of Dog is Maximus.
4 Maximus is a Pitbull with an Omnivorous diet
5 Maximus is owned by John, who lives in the USA

Questions

Anonymous Structs

An anonymous struct is a struct you can declare and initialize on the fly without explicit declaration via type. It’s declared in an inline manner along with your code, which provides a flexible way of declaration and usage of your data structures.

From a developer’s perspective, this provides speedy invocations of your data structures when you need it because anonymous structs can immediately invoke your data structures as needed along with your code. Other cases where anonymous structs are useful are when a name is not needed for the operation.

Anonymous struct also provides a way to avoid needless declarations of type name alias, which can cause namespace pollution for your struct. However, it’s recommended to avoid deep nested anonymous structs due to the risk of unreadable code. This may result in a code that is hard to maintain.

Finally, anonymous structs is a good and cheap alternative to an empty interface{} type.

 1 package main
 2 
 3 import (
 4   "fmt"
 5 )
 6 
 7 func main() {
 8   Animal := struct {
 9     Kind string
10     Diet string
11   }{"Dog", "Omnivorous"}
12 
13   fmt.Println(Animal.Kind, "-", Animal.Diet)
14 }
1 $ go run anonymous_struct.go
2 
3 Dog - Omnivorous

Questions

Anonymous Struct Fields

In OOP languages, the properties of an object are collectively called attributes. Since Go is not an OOP language, attribute names in an OOP property are called fields.

Normally, a struct has a declared field name with an attached type. However, this is not always the case. You can also create a struct without field names, leaving only its type.

The way to access the value of an anonymous field is by calling the literal name of the field type of the struct, for example:

 1 package main
 2 
 3 import (
 4   "fmt"
 5 )
 6 
 7 func main() {
 8   animals := struct {
 9     int
10     string
11   }{1, "Dog"}
12 
13   fmt.Println(animals.int)
14   fmt.Println(animals.string)
15 }
1 $ go run anonymous_struct_fields.go
2 
3 1
4 Dog

You can only access the value by calling the literal name of the type in the struct, and with this semantics, it comes with a limitation.

It is important to note that when using anonymous field names, you have a limitation of having a declaration of the same type only once since anonymous struct fields need to have unique value types. Otherwise, there cannot be a distinction between each type.

Questions

Chapter Questions