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

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

Welcome to the third in my series on learning to develop in GoLang (Go) in the first two articles, which can be found here:

  1. It is time to “Go” and learn how to code with Go
  2. It is time to Go: our journey into grown-up code part 2

To date, we have discussed several core features of the language, setting the foundations of knowledge.  We have covered:

  • Variables (Strings)
  • Variadic variables (Strings)
  • Redeclaration
  • Constants
  • Structs
  • Pointers
  • Interfaces
  • Function basics

Today we will move away from “hello world”; to be fair, I think we have done “hello world” to death. So in this article we will not develop any new code but will create snippets as examples as we dive deeper into inputs, variable types and conditional statements.  One thing you will note is that I will start to just focus on the core of the knowledge.  if you wish to follow the code completely remember to insert your package and import statements.

But before that, let’s be a little interactive.  Up to now, we have only scraped the surface “fmt” package capabilities.  If that was all we could do, then Go would be a very limited language. “fmt” can also take input.  Take a look at this code

package main
import (
            . "fmt"
            . "strings"
)

func main (){
            Println(“Hi, what is your name?”)
            var name string
            Scanln(&name)
            name = TrimSpace(name)
            Printf(“Hi, %s!  I’m Go Pleased to meet you.”, name)
}

You will recognise much here in this simple program.  We have the importation of the “fmt” library and a function called main.  However, this time we are also importing a second library called “strings” we can introduce this with a simple import . “strings”, but adding “()” to import allows us to import multiple libraries in a single command.

Our Main function contains one variable declaration called “name”, which is of the type string. Next, We are print to the standard output the question “Hi, what is your name?” with the line Println(“Hi, what is your name?”).  The third line of the function uses “Scanln()”; this is new to us; it tells Go to wait for input from the keyboard. “Scanln()” will capture any keys pressed into the variable name as shown by “Scanln(&name)”; this command is escaped with a press of the return key (ENTER).   Our next line introduces the first command in the “Strings” package “TrimSpace”; this will remove any white space from the start and end of a string that is input to it.  Our last time is the focus of the function; here we introduce “Printf()”; this is part of the “fmt” library and takes a named string and using a special printing verb “(%s)” injects the variable into whatever is written between the two brackets.  If you are used to Terraform, this may seem odd as you will be used to using string interpolation to insert the value of a variable into another string.  Running this code will result in the following response:

Hello I am Go, Please to Meet you.
Now isn’t that nice and polite?

Now that we have started to look deeper into packages, having imported a new one.  It is time to discuss the differences between the $GOROOT and $GOPATH variable and the “src” directory, which appears under both.  Both variables are needed for Go to be successfully installed and configured. “$GOPATH” points to the path in the user’s working directory, usually “~/go” in this directory, we have a “pkg” and an “src” directory.  These directories store things we have created and any third-party packages that need to be imported.  Looking in the “src” directory, we can see several directories have already been imported.

GoPath and GoRoot
Oh look, there are 3rd Party things in our src folders

The second environment variable, $GOROOT, specifies the Go distribution’s root directory, which contains all of the packages for the standard library. Users never need to set GOROOT because the go tool uses its default location after installation.  Again, you will see a “pkg” and an “src” directory together with several others.

What is in GoRoot
GOROOT it as little more cluttered

Moving into the “src” directory you can see several folders contained within, these folders include some you should already recognise the “fmt” (the package that contains println, printf, scanln, etc) and “strings” folders (the package that contains “TrimSpace”). Let’s move into the “fmt” folder and open the “print.go” source file in your favourite editor.  This may at first appear confusing but looking deeper you will notice several functions that we have taken advantage of in our “hello world!” code sets; for example “Printf()”, “Println()”.  You should also find that we can recognise quite a lot, we can see that the package is called “fmt”, we have an import block, we are declaring constants and variables, creating interfaces and finally functions.  With some work, we should figure out what is generally going on, but there are several constructs we have not yet been exposed to like case, several actions and variable types like Integers (int),  Booleans (bool), etc.

A deeper look into variable types

So far, we have only looked at one variable type that of a string; this represents a single word or a number of words.  However, there are other types of data.  In Go there are three common data types:

  • bool: a representation of a Boolean value; this is a true or false statement.
  • strings: a representation of a word or a set of words.
  • Numeric: representations of numeric values.

The numerical values are further sub-divided into:

INTEGERS: In Go language, both signed and unsigned integers are available in four different sizes as shown in the below table. The signed integer is represented by “int” and the unsigned integer is represented by “uint”. Another important point to understand about an integer is that they are whole numbers (1, 16, 26); they cannot be fractional (2.6, -26.35).

The next obvious question is what is the difference between a signed and an unsigned integer, the answer is not what you may think,  it has nothing to do with certificates and PKI.  It relates to the range of the value the integer can take.  For example, INT8 is a 8 bit integer that can store a value of -128 through to 127 whereas UINT8 is an unsigned 8 bit integer that stores only positive values and is in the range of 0 to 225

Data Type Description  Values
int8 8-bit signed integer (-128 to 127)
int16 16-bit signed integer (-32768 to 32767)
int32 32-bit signed integer (-2147483648 to 2147483647)
int64 64-bit signed integer (-9223372036854775808 to 9223372036854775807)
uint8 8-bit unsigned integer (0 – 255)
uint16 16-bit unsigned integer (0 – 65535)
uint32 32-bit unsigned integer (0 – 4294967295)
uint64 64-bit unsigned integer (0 to 18446744073709551615)
int Both int and uint contain same size, either 32 or 64 bit.
uint Both int and uint contain same size, either 32 or 64 bit.
rune It is a synonym of int32 and also represents Unicode code points.
byte It is a synonym of uint8.
uintptr It is an unsigned integer type. Its width is not defined, but its can hold all the bits of a pointer value.

You may have noticed that we have not mentioned the rune, the “byte or the uintptr these are special types of variables,  the “rune” is an alias for a int32, however, the “rune” can contain special Unicode characters for example the “Ö” ((umlaut) Unicode value 214) as a single entity, however, using “byte” which is an alias for an “int8” will result in two “int8” values. Now I found it difficult to find a decent definition of “uintptr,” but this stack overflow came the closest to removing the veil from my eyes; where the answer stated that it is primarily used for “unsafe” purposes.

FLOATING-POINT Numbers:  These are used to represent real numbers that cannot be expressed as integers. Real numbers include all rational (numbers that can be written down completely for example 7.6) and irrational numbers (are numbers that cannot be written down for example π), and because of this, floating-point numbers can contain a fractional part, such as 9.0 or -116.42. at a very high level it is a number that contains a decimal point.

Data Type Description
float32 32-bit IEEE 754 floating-point number
float64 64-bit IEEE 754 floating-point number

COMPLEX NUMBERS: The complex numbers are divided into two parts are shown in the below table. float32 and float64 are also part of these complex numbers. The in-built function creates a complex number from its imaginary and real part and in-built imaginary and real function extract those parts

Data Type Description
complex64 Complex numbers which contain float32 as a real and imaginary component.
complex128 Complex numbers which contain float64 as a real and imaginary component.

This is perhaps better explained with a bit of code.

package main
import (
    "fmt"
)

func main() {
    c1 := complex(2, 3)
    c2 := 4 + 5i // complex initializer syntax a + ib
    c3 := c1 + c2 // addition just like other variables
    fmt.Println("Add: ", c3) // prints "Add: (6+8i)"
    re := real(c3)           // get real part
    im := imag(c3)           // get imaginary part
    fmt.Println(re, im)      // prints 6 8
}

Running this snippet results in the following response. Here we declared a complex variable of 2, and 3 in c1, then we declared another variable c2 which is assigned the values of 4 and our imaginary number of 5i.  the final variable c3 is a simple addition of c1 and c2.

complex numbers in Go
Oh, so that is what a complex number is.

The next thing we are going to look at is conditional statements and case statements.

Ever-decreasing circles, conditional statements, and Case statements.

We were introduced to our first decision tree or conditional statement in our second article this was a simple if/else statement.  This is what is termed a conditional statement, it is one of the simplest decision trees available in Go.  Even simpler is the “if” statement, here your statement is either true or the program exits,

If condition {
            fmt.Println(“condition met”)
            }
            fmt.Println(“program terminated”)
}

Next we have an “if/else” construct, this is where if one condition is not met, a second condition will run, in other words, condition one is tested and if it is not met, the second condition is implicit and will run.

func main (){
            a := 2
            If a > 10 {
                        fmt.Println(“condition met”)
                        } else {
                        fmt.Println(“condition was not met , so run me”)
            }
}

The next condition is the “else if” condition, this is used to iterate between multiple conditions,

func main () {
            fruit := “orange”
            If fruit == “mango” {
                        fmt.Println(“fruit is mango”)
            } else if fruit == “orange” {
                        fmt.Println(“fruit us Orange”)
            {fmt.Println == “banana” {
                        Println(“fruit us banana”)
            } else {
                        Println(“I don’t which fruit this is”)
            }
}

There is also a construct called a “switch.  The switch statement takes a single input value and matches it against a large number of cases. This is used to simplify multiple if-else statements, Go does not require a break statement to end a case block. The case block and switch statement will both terminate when the last statement in the case block executes. In Go, duplicate cases (values) are not permitted.

Func main() {
            finger := 2
            switch finger {
            case 1
                        Println(“thumb”)
            case 2
                        Println(“Index”)
            case 3
                        Println(“Middle”)
            case 4
                        Println(“Ring”)
            case 5
                        Println(“Pinky”)
            }
}

If the finger is not a value between 1 and 5 there will be no output from this particular switch statement.  Which brings us on to the “default” case.  This is a catch all value

func main() {
            finger := 6
            switch finger {
            case 1
                        Println(“thumb”)
            case 2
                        Println(“Index”)
            case 3
                        Println(“Middle”)
            case 4
                        Println(“Ring”)
            case 5
                        Println(“Pinky”)
            default
                        Println(“no finger matched”)
            }
}

You can also create multiple case values separated by a comma,

Func main() {
            letter := “e”
            switch finger {
            case "a"," e"," i"," o"," u"
                        Println(“The letter is a vowel”)
            default
                        Println(“The Letter is not a vowel, but a consonant”)
            }
}

It is great for “strings” and “Numbers,” but can be a little restrictive as an input or expected answer is required.  Another thing of note is that the input value on the switch statement is optional, it if is excluded, case will use a simple “yes/no”, or to be precise a Boolean, to infer the response.

func getnumber () int {
            return 30
}

func main() {
            switch number := getnumber(); {
            case number <= 10:
                        Println(“the number is less than 10”)
            case number > 10:
                        Println(“the number is Greater than 10”)
            Case number > 20:
                        Println(“the number is greater than 20”)
            Case number > 30:
                        Println(“the number is greater than 30”)
            }
}

It seems a little restrictive having to infer the answer before running the switch statement, well we can infer the answer from a variable as we have shown in the example above. A semicolon is required after the initial statement because without one, our switch statement becomes expressionless and “number:= getnumber()” becomes the input value expression, which is obviously wrong.

Emmm,I like case it looks powerful, but one problem with case is, is that it when case finds an option that meets the input requirement, the task will execute and the programme will exit the switch block, 9 times out of 10 this is what you want to happen; let’s revisit the code above, can you see what response will return? Yep it will return the second answer, as 30 is greater than 5; but that is not the expected answer. In this case, you would expect the returned answer would be the last statement. Now we could change the greater than “>” to read “<” and the answer would work. However, there is another option and that is “fallthrough.” One thing to note is that there is no fallthrough statement in the last case statement as there is no further condition to fall into.

func getnumber () int {
            return 30
}

func main() {
            switch number := getnumber(); {
            case number <= 10:
                        Println(“the number is less than 10”)
                        fallthrough
            case number > 10:
                        Println(“the number is Greater than 10”)
                        fallthrough
            Case number > 20:
                        Println(“the number is greater than 20”)
                        fallthrough
            Case number > 30:
                        Println(“the number is greater than 30”)
            }
}

Running this code will result in the following:

Switch fallthrough
see there were three options that worked.

Summary

This article has covered a lot of ground again. we have discussed inputs, discussion on variable types, and conditional statements, these are major building blocks of “proper” coding languages.  We were going to discuss loops but as this article is already over 2000 words, we will start our fourth article explaining loops.  This article has been a hard read, but worth it.

NEWSLETTER

Receive our top stories directly in your inbox!

Sign up for our Newsletters

spot_img
spot_img

LET'S CONNECT