HomeDevelopmentIt is time to Go: our journey into grown up code part...

It is time to Go: our journey into grown up code part 2

As we start our second article chronicling my journey into Go, let’s review what we learn in the first forage into learning a proper coding language.

In our first article, we wrote a small but functional “Hello World” application and started improving it with variables and constants, introducing the concept of an augmented assignment operator.  Today, we will delve deeper into Go; we will still use “hello world” as the premise for the principles we develop today.  As a reminder, here is the current state of our code.

package main

import .  "fmt"

const (
        firstWord = "Hello"
)

var (
      secondWord = "World!"
)

func main () {
        // This is the ubiquitous first sample program.  The great and awesome Hello World
        secondWord := secondWord + " Bang"
        Printf("%v %v\n",firstWord,secondWord)
}

The first thing we are looking at today is the concept of a local variable; we will also introduce another “coding word” into our vocabulary, “redeclaration”.  A redeclaration is any change in value to an already declared and assigned variable.  Open your editor, load your file and change the line:

secondWord := secondWord + " Bang"

to read:

var local string
local = secondWord + " Bang"

if you run this code, you will see that the result is exactly as before.

Our newly declared variable “local” has a “string” type and is only available within the function.  This is similar to the concept of the local variable in a terraform module.  Now guess what the name  of the variable outside of the function is called?  Yep, it is a global variable.

One thing to note is the original “secondWord” variable is still capable of being used within our function because we did not redeclare the “secondWord” variable, but created a new version called local to replace it.  Do you want proof?  OK then.

Change the “Printf” line to read:

Printf(!%v %v\n %v\n”),firstWord,local,secondWord)

Running the code this time will result in the following:

Go - Nothing new hereThe result is the same.

I am unsure if that will be useful, but it is interesting to know.

Another thing to note about variable declarations in Go is that they are always initialised with a zero value.  So in the case of our local variable in our function above, our variable “local” as it is a string will actually have the value “”, or to be precise, a string containing no characters.

Let’s look at Functions, these help us Go Faster.

Now that we have looked at referencing values in Go and how to display them using the “Println()” function, it is time to a look at creating our own.  We have already implemented a function, “main()”.  This function hints at what’s involved with their creation, but “main()” is a special case as it allows a Go program to execute and does not require any parameters or produce any values to be used elsewhere in the program.

Before we start let’s return our “Hello World” code back to using “Println()” and removing our local variable.  You code should now look like this.

package main
import .  "fmt"
const firstWord = "hello"
var secondWord = "world"

func main() {
   Println(firstWord, secondWord)
}

Next, we will create our first function.  We will create a new function called “secondWord()”; this function will to the outside world, have the same operational purpose as the variable of the same name used previously.

func secondWord() (string) {
  return "world"
}

The empty brackets “()” are used to indicate that no parameters to be passed into the function when it’s called, The “(string)” value states that a this function will return a single value and it is of the type “string”.  Anywhere that a valid Go program would expect a string value, we can instead place a call to “secondWord()” and the value returned will satisfy the compiler.  The use of “return” is required by the language whenever a function specifies return

values, and in this case, it tells the compiler that the value of “secondword()” is the string “world”.  You code should look similar to that shown below.

package main
import .  "fmt"
const (
        firstWord = "Hello"
)
var (
        secondWord = "World!"
)

func main () {
        // This is the ubiquitous first sample program.  The great and awesome Hello World
        Println(firstWord, world()) //secondWord)
}

func world() (string) {
        return "world"
}

Running this code will result in the following nothing new to see here:

Go Hello World
Just Good Old Standard Go Hello World again

One Piece of Information: Apparently, Go is unusual for a coding language as a function can return more than one value, and as such, each function takes two sets of (), the first for parameters and the second for results.

We can look at two other methods to achieve the same result.  Firstly we would look at a function called “message”:

package main
 import "fmt"
 func main() {
   fmt.Println(message("world"))
}

 func message(name string) (message string) {
   message = fmt.Sprintf("hello %v", name)
   return message
}

The message() function, like our previous world() function, can be used anywhere the Go compiler expects to find a string value.  Message(), on the other hand, performs a calculation using the Sprintf() function and returns the result, whereas world()returns a predetermined value.

Sprintf() is similar to Printf(), except instead of creating a string and displaying it in the terminal, it returns this string as a value that we can assign to a variable or use as a parameter in another function call like Println().

package main
import . "fmt"
 func main() {
   Println(message("world"))
}

 func message(name string) (message string) {
   message = Sprintf("hello %v", name)
   return message
}

Remember, if you add a “.” then you do not have to reference “fmt” on the Println () and Sprintf () statements.  One thing to note is we are only importing a single function; things can get a little complicated when there are several.

The final construct we will look at is a “variadic function”.  A “variadic function” is a construct that has a varying number of arguments.  In other words, the variadic function can accept zero or many arguments from the user.   You may recognise this from the earlier use of “fmt.Printf”.  That is an example of a variadic function; it requires one fixed argument at the start and can accept any number of arguments afterwards.

Note: When declaring a variadic function, the last parameter type is always preceded by an ellipsis, (the three periods symbol …).  It indicates that the function can be called with any number of parameters of the defined type.

package main
import .  "fmt"

func main() {
  print("Hello", "world")
}

func print(v ...interface{}) {
  Println(v...)
}

There are several things requiring explanation in this snippet of code.  To start, we have added a new type, “interface”, which serves as a proxy for any other “type” in a Go program.  We will dive deeper on the details later, but for now, it is enough to know that we can provide a string anywhere an interface is accepted.

We use “v…interface” in the function signature to declare a parameter “v” that can take any number of values (remember “v” is just what we called it; it could be “big-purple-monster” it does not matter, just be consistent).  These are received as a sequence of values by “print()”, and the subsequent call to “Println(v…)” uses the same sequence because it is the sequence expected by “Println()”.

So, why use “…interface{}” instead of “…string” to define our parameters?  Because the “Println()” function is defined as “Println(…interface{})”, we must also use “…interface{}” in the type signature of our function to provide a sequence of values en masse.  Otherwise, we’d have to make a “[]interface{}” (a slice of interface values, do not worry about the concept of Slice’s at the moment, we will be getting to them soon as they are a core concept of Go) and then copy each individual element into it before passing it to “Println()”.

If you are still with me, hopefully, you are and have not been frightened off yet.  It is time to start to complicate matters even more and start discussing the concepts of “encapsulation”.

Let’s complicate things.

Let’s look at encapsulation, OK that is new.  What is Encapsulation?  A reasonable explanation of encapsulation is that it “wraps data into a single unit”, and functions as a barrier that stops code from outside the barrier from accessing the data. To explain this better it is time to mix things up a bit; this is the code we will be looking at now.

package main
import . "fmt"
type Message struct {
  X string
  y *string
}

func (v Message) Print() {
  if v.y != nil {
    Println(v.X, *v.y)
  } else {
    Println(v.X)
  }
}

func (v *Message) Store(x, y string) {
  v.X = x
  v.y = &y
}

func main() {
  m := &Message{}
  m.Print()
  m.Store("Hello", "world!")
  m.Print()
}

Yikes, that looks scary!  This is the code of nightmares.  These are the sort of things that turned me off trying to learn code 20 yrs ago.  All this just to display “hello world!” on a CLI line, you cannot be serious!   Stop!  Don’t run off.  There is a lot in this code that we should be familiar with, we can see that three functions are declared, we can see that we are importing “fmt” again.  So what is new?  The first thing we can see is a new construct called a Struct” this looks complicated, but at its core, it is just a container to allow the grouping of inputs.

type Message struct {
  X string
  y *string
}

Here we have declared two values, In this case, two strings, X and y as a part of this “Struct.”

There are two things to consider here:

  • Go deals with an uppercase and lowercase identifier differently. If the lead character of a type, constant or variable is uppercase then that value is available to other packages (more about that in a later post).  If it starts with a lowercase letter, then it is only available within that package.
  • The declaration of the variable “y” has a “*” character in front of the string declaration.

This is an example of a pointer.  What this does is effectively allow for a value to be empty.  So basically.  this means that “X” is a string value that must be present and will be a value that is available to other packages, and “y” is a string value that may be present and will only be open to the package.

If we will look at the” main()” function first:

func main() {
  m := &Message{}
  m.Print()
  m.Store("Hello", "world!")
  m.Print()
}

In Go, “:=” is called a short variable declaration and is used as a shortcut to declare and assign a value to a variable in one pass.  The “:” is the declaration, and “=” is the variable assignment.  Next, we are introduced to the cousin of “*” the “&”.  Remember, the “*” is a pointer to a memory address that we stored the variable, and the “&” is what we used to pull that value into use.  So here we are saying that “m” is a variable that is declared and assigned the value which is stored in “*Message{}”.  This is declared in the following function:

func (v Message) Print() {
  if v.y != nil {
    Println(v.X, *v.y)
  } else {
    Println(v.X)
  }
}

This function introduces another concept, the if/else statement, which is not contained in terraform and may be new to you.  It is a simple test function; if this option Adoes not work or fit, then run the second option.

The main function further describes the inputs and the outputs, “m.Store” declaration holds the values of X and y.  This is passed to the second function which creates the values for X and y; taking the input of “m.Store” from the main function and adding it to the aforementioned “*Message{}” value.

func (v *Message) Store(x, y string) {
  v.X = x
  v.y = &y
}

Encapsulation is a massive benefit when you are writing complex programmes. So why introduce the concept now.  The TL:DR answer is that it gets you used to thinking about it; the longer one is that it is the foundation for several other constructs and enhances other things that we have already looked at like “Interfaces“.  An Interface is similar to a struct but it defines the type in terms of a set of method signatures with which it must be implemented with.

Summary

What is interesting about the code we have shown above, is that even though we have shown several methods of work, each more convoluted than the previous, the end result is the same a Go program displaying “Hello World!” to a terminal prompt.  This may seem like an exercise in explaining why “K.I.S.S” (keep it simple stupid) is essential, but, it does show that there are many to solve even a simple problem.  Go is not a simple language to learn, as we have shown.  But consider what we have covered already in these first two articles of the series:

  • Variables
  • Variadic variables
  • Redeclaration
  • Constants
  • Structs
  • Pointers
  • Interfaces
  • Function basics

To date, we have only scratched the surface of the language.  But already we have discussed some complex concepts.  In our next article will will move away from “Hello World” and start see some of the power of Go.

NEWSLETTER

Receive our top stories directly in your inbox!

Sign up for our Newsletters

spot_img
spot_img

LET'S CONNECT