Go Upskilling Course


Introduction to Golang

Go is strong typed language project created by Robert Griesemer, Rob Pike and Ken Thompson, the language has a lot of roots in C.

Go is the Schrödinger cat of object orient programing language since it is and at the same time it is not an OOP. Since there is no type hierarchy, but relies in interface to satisfice polymorphism.

More about the origins of go here.


Getting Started

To download the go compiler just follow the following instructions

Environment

The go compiler is expecting a structure for the imports, to do this it relays in the $GOPATH env variable, this variable will tell the go compiler were to found the modules to import. The $GOPATH will contain a directory with 3 folders: src, pkg and bin.

bin

The bin directory contains the binary files installed by the command go install

pkg

The pkg directory contains the precompiled packages for future project compiles.

src

The src directory contains the source code of the projects. Since is usual to integrate github in the projects, it is common that the structure of the src directory contains a github.com/user/project.


Writing Code

First let's check that the installation is working, for this we will create a new folder inside the src directory in the $GOPATH

cd $GOPATH
mkdir example

inside the directory we will create the file hello.go with this content:

package main func main()
{
    fmt.Printf("hello, world\n")
}

Run it like a script!

$ go run hello.go

or compile it!

$ go build

Note: go build utilises the working directory as a target for compilation, you can also pass the path as a param.

And run it

$ ./hello

Packages

A package is a library in Go, If a package name is main go will search for the code for the main function and compile the project as an executable; otherwise the package is compile as a library

A project can contain multiple packages, each will be represented as a folder in the src directory, packages can contain more than a file, the scope of the package are all files in the folder.

Importing and exporting Go has 2 access settings for packages, exported and unexported. All names of methods, functions, variables, constants and interfaces that start with a capital letter are exported and all that not are unexported.

Unexported( something like private) access mean that it can be access from other files in the same package, but cannot be access outside the package scope.

Exported(Something like public) access mean that it can be access from other packages when the package is imported. All exported variables, interfaces, functions, methods and constants have to be commented.

Importing packages is done with $GOPATH + the directory of your local package or the external package

import (
    "fmt"
    "github.com/user/hello/example"
    "github.com/google/go-cmp/cmp"
)

During the import of the package one can overwrite the name of the package.

import sql "mysql"

In the example adobe we are importing the mysql package and renaming it "sql" if in a moment in time me want to change from mysql to sqlserver or another sql driver, we just need to change the imported packaged and all the code can remain the same.

Another form of importing packages using the underscore symbol, this will import the package only for his side-efects (inititalition funcitons)

More info

https://golang.org/doc/code.html

https://golang.org/doc/effective_go.html#package-names

More about go tool can be found with the go help command


Variables

Golang support the following built-in types:

For strings:

For Boolean values:

For numeric types:

A variable can be declared in the following ways:

var name string
var name1, name2 string = "value", "value2"

A short way to declare a variable is using the := operator, that is used to declare a variable with an implicit type based on the right side of the operation.

name := "1"
name1, name2, name3 := "value", "value2", "value3"
i := 1
i,j,k := 0, 1, 2

Strings and Runes

Strings by default are UTF8, with this using a char is not possible, since the size of a char is 1 byte, and a UTF8 symbol can use up to 4 bytes (32 bits), to solve this problem runes are used to avoid having problems with unicode sets.

Example

package main

import (
    "fmt"
)

func printByChar(s string) {
    chars := []byte(s)
    fmt.Printf("This slice has a len of %d\n", len(chars))

    for i, c := range chars {
        fmt.Printf("Char %d value is %d\n", i, c)
    }
}

func printByRune(s string) {
    runes := []rune(s)
    fmt.Printf("This slice has a len of %d\n", len(runes))
    for i, r := range runes {
    fmt.Printf("Rune %d value is %c\n", i, r)
    }
}

func main() {
    hello := "Hello world 👋🌎"
    hello2 := "Hello workd!!!"
    fmt.Printf("String 1 has a len of %d\n", len(hello))
    fmt.Printf("String 2 has a len of %d\n", len(hello2))

    printByChar(hello)
    printByChar(hello2)
    printByRune(hello)
    printByRune(hello2)

}

Output:

String 1 has a len of 20
String 2 has a len of 14
This slice has a len of 20
Char 0 value is 72
Char 1 value is 101
Char 2 value is 108
Char 3 value is 108
Char 4 value is 111
Char 5 value is 32
Char 6 value is 119
Char 7 value is 111
Char 8 value is 114
Char 9 value is 108
Char 10 value is 100
Char 11 value is 32
Char 12 value is 240
Char 13 value is 159
Char 14 value is 145
Char 15 value is 139
Char 16 value is 240
Char 17 value is 159
Char 18 value is 140
Char 19 value is 142
This slice has a len of 14
Char 0 value is 72
Char 1 value is 101
Char 2 value is 108
Char 3 value is 108
Char 4 value is 111
Char 5 value is 32
Char 6 value is 119
Char 7 value is 111
Char 8 value is 114
Char 9 value is 107
Char 10 value is 100
Char 11 value is 33
Char 12 value is 33
Char 13 value is 33
This slice has a len of 14
Rune 0 value is H
Rune 1 value is e
Rune 2 value is l
Rune 3 value is l
Rune 4 value is o
Rune 5 value is
Rune 6 value is w
Rune 7 value is o
Rune 8 value is r
Rune 9 value is l
Rune 10 value is d
Rune 11 value is
Rune 12 value is 👋
Rune 13 value is 🌎
This slice has a len of 14
Rune 0 value is H
Rune 1 value is e
Rune 2 value is l
Rune 3 value is l
Rune 4 value is o
Rune 5 value is
Rune 6 value is w
Rune 7 value is o
Rune 8 value is r
Rune 9 value is k
Rune 10 value is d
Rune 11 value is !
Rune 12 value is !
Rune 13 value is !

In this example we can see that the two string has the same number of characters both have different length, since one has unicode characters, but when a rune is used to read it, both have the same size.

Alias

Alias are a way of renaming a type of variable, for example: A byte and rune are alias and int32 respective, this mean that they are not different types of data but they have an alternative spelling.

This is something similar of int and char in C, you can do operation with them since they have the same size, but is not the same since Go will not let you operation between then because they have the size, but since they are the same type but with different name.

Constants

Constants works like another language and have to follow this rules They have to use they keyword cons to be declared Can only be can be character, string, boolean, or numeric values. Cannot be declared with the := shortcut

const name = "value"

Zero values


Arrays, Slices and Maps

Arrays

Arrays are of specific types and length, their length is fixed it cannot grow or reduced, arrays are values in Go, this meaning that the array variable is all the array, instead of the pointer at the first element of the array.

Every array length type is the same, it will make [3]T a different type from [4]T. To declare an Array the following syntax is used:

var name int[3]
    arr := [3]int{1,2,3}
    arr := [...]int{1,2,3,4,5,6}

Using ... in the declaration of the array will instruct the compiler to count the variables at moment of compilation.

Slices

Slices in the hand are a more versatile type of data, a slice consist in the following:

To create a slice the function syntax is used:

make([]T, len, cap)

It will return an slice of type T ( []T ). The length and capacity of the slice can query with the funcitons len(s) and cap(s).

Slices can dynamical grow to adjust to the desired length of the slice. This is done internally by the append function, when the new array index is bigger than the capacity of the slice, it will create and copy elements to the new array.

func main() {
    s := make([]int,0,3)
    for i := 0; i < 7; i++ {
        s = append(s, i)
        fmt.Printf("cap %v, len %v, %p\n", cap(s), len(s), s)
    }
}

Output

cap 3, len 1, 0xc0000b8000
cap 3, len 2, 0xc0000b8000
cap 3, len 3, 0xc0000b8000
cap 6, len 4, 0xc0000ac030
cap 6, len 5, 0xc0000ac030
cap 6, len 6, 0xc0000ac030

We can see in the example above the function append will duplicate the capacity of the

The slices have 3 built-in functions

Map

Go implementation of the Map uses a hash table, it have to follow this rule: the keytype of the hashmap has to be comparable type. The map has the following built interfaces:

Maps can be checked if a key exists using a two initialization variables:

myMap := make(map[int]string)
...
val, ok := myMap[324]

The variable val will be assigned the value of the content of the hashmap if it contain the key, and the variable ok will be assigned true or false depending if the map content the key


Control Flow

if/else

if/else syntax is the same as java or c, but without the () and the {} are required.

if a == b {
    // ...
} else if a == c{
    // ...
} else
    // ...
}

Also it is possible to create a variable to have a initialization statement at the start of the if:

if val, ok := myMap[324]; ok{
    //...
}

In the example abode the if will check if the map contains the key.

for

For cycle is almost the same as C/Java, but the () are gone and the {} is required, also the for in Go can be used as a while.

Simple for loop

for i := 0; i < 10; i++ {
    // ...
}

It can be used like a while, with only the condition

for i < 10 {
    i++
}

Or it can be used to have an infinity loop

 for {
    // ...
}

Switch

Switches are very similar to C/Java but instead of using the “break” keyword the code only execute one case and will not continue with the following cases unless the keyword “fallthrough” is used. Some other rules for the switch are that the cases can contain multiple values, () are removed from the syntax and {} are mandatory.

Example

switch integer {
    case 1:
        // code1
    case 2, 3, 5, 7:
        // code2
        fallthrough
    case 4:
        // code3
    default:
        // code4
}

In this example if condition1 is meet only code1 is run, but if condition2 or condition3 is meet the code2 and code3 is executed, but for condition4 just code3 is executed, if none condition is meet code4 is executed.


Functions

Functions in Go start with the keyword “func” followed by the name of function, input values and return values

Example

func example()
func example(x int) int
func example(a, _ int, z float32) bool
func example(a, b int, z float32) (bool)
func example(prefix string, values ...int)
func example(a, b int, z float64, opt ...interface{}) (success bool)
func example(int, int, float64) (float64, *[]int)

Returning values is done with the keyword “return”, it can return multiple values if the function expects multiple return values

Defer

Defer are instructions that are always executed at the end of the function, they can be stacked. When something is deferred it is not executed in that moment, it enters a stack and is keep it there until the function ends or a early return is find, then there are executed in last-in first-out order.

package main

import "fmt"

func main() {
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")
}

The example above will print the following:

counting
done
9
8
7
6
5
4
3
2
1
0

Pointers

A pointer is a special variable used to store a memory value, this mean that instead of the variable containing a value, it contains the value of the direction on memory of the variable.

This allows having some of the most important functions, allowing the variables be passed as reference in the functions instead of been passed as values.

The pointer has 2 reserves operators * and &, & can be used before the variable name and it will return the variable memory location. * can be used before the type of store value in the declaring and it will mean that is a pointer, and also is used to deference pointer values. When differencing a pointer, we will access to the value stored instead of the memory location of the value.

Something that we always keep in mind is that in Go does not support pointer arithmetic like C/C++.

package main
import "fmt"

func passAsValue(variable int){
    fmt.Println("Memory dir inside value func ", &variable)
    variable = 10
    fmt.Println("Value inside value func ", variable)
}

func passAsReference(variable *int){
    fmt.Println("Memory dir insde reference func ",variable)
    *variable = 20
    fmt.Println("Value inside reference func ", *variable)
}

func main() {
    variable := 1;
    fmt.Println("Memory dir inside main func " , &variable)
    passAsValue(variable)
    fmt.Println("Value after pass as a value " ,variable)
    passAsReference(&variable)
    fmt.Println("Value after pass as a reference", variable)

}

Output:

Memory dir inside main func  0xc000018080
Memory dir inside value func  0xc000018088
Value inside value func  10
Value after pass as a value  1
Memory dir inside reference func  0xc000018080
Value inside reference func  20
Value after pass as a reference 20

Errors

The package errors contains the interface error and the method New  that return a error instance.

type error interface {
    Error() string
}

By convention, errors are the last return value and have type error, if the function has no error is common to return a nil otherwise errors.New return a new error with the given message.


Type

The type keyword is used to create a new type, it can be used to create structs, interfaces or new alias for a existing type

type name struct{...}
type name interface{...}
type myNumber int32

Structs

Structs are a collection of fields with declared types. Structs can be initializaded by a JSON like syntax.

package main

import "fmt"

type person struct{
    name string
    age int
    address string
}

func main() {
    p := person{
        name :"my name",
        age : 28,
        address : "my address"
      }

    fmt.Printf("name: %s\nage: %d\naddress %s\n",p.name,p.age,p.address)
    p.name = "change of name"
    p.address = "change of address"
    fmt.Printf("name: %s\nage: %d\naddress %s\n",p.name,p.age,p.address)
}

Output:

name: my name
age: 28
address my address
name: change of name
age: 28
address change of address

Interfaces

Interfaces help specifying the behaviour of the objects. For an object to implement an interface it should follow one simple rule, it must suffice all methods declared in the interface.

An object can implement multiple interfaces.

Declaring is done with the keyword interface as follow:

type Store interface{
    Get(string) (string, error)
    Set(string, string) error
    Delete(string) error
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

In this example, if a struct wants to implement the interface Writer, it should implement the method Write


Methods

For an object in go to implement an interface it needs to suffice all the methods declared in the body of the interface, for an object to do this it just need to declare a function pointing to the object.

Example:

//Using as reference the method declared in the Writer interface
type console struct{
   ...
}

func (m *console) Write (n int, err error){
    //body....
    return 0, nil

}
//Now the struct console is implementing the interface Writer since it suffice the interface implementing all methods declared

Documentation

Go have is own documentation generation tool called godoc this tool, godoc is simple than Javadoc or DocString from python since the comment are not treated as language constructs or have is own machine syntax. To document anything in go just write a comment at the declaration and with no intervening blank line before it.

All exported types, interfaces, methods and structs has to be commented.

More information and conventions here


Test and Benchmarks

Test

The go tool provide his own automated testing for go source code, it is run by the command

go test

And will search for the files ended with _test.go and for the test cases all routines that have the following format

func TestXxxx(*testing.T)

The methods ErrorF, Fatal, Error or any other related method can be used to signal a non success test, all of those methods are declared in the package testing

Benchmarks

Benchmarks are a type of test that is run b.N times until it can be long enough to be properly timed. The variable b.N is auto adjusted until it have the correct size. Benchmarks follow the following expression: func BenchmarkXxx(*testing.T)

benchmarks are rune with the go tool:

go test -bench

Anonymous functions and clousures

Anonymous Functions

An anonymous functions is a function that does not have a name, they are created dynamically, like the variables, they can be described as follow:

dynamic := func() {

}

This allow to have flexibility at the momment of writting code

import "fmt"


func printHello() {
   fmt.println("Hello")
}

func printBye() {
   fmt.println("Bye")
}

func main() {
   //We set the value of myVar to an anonymous function
   myVar := func() {
       println("stuff")
   }
   myVar()

   //We assing the funciton to the variable, changing the anonymous function
   // for printBye
   myVar = printBye
   myVar()

   myVar = printHello
   myVar()

   //We can overwrite the non anonymous function for an anonymous one
   myVar = func() {
       fmt.println("something")
   }
   myVar()
}

In the example abode we have a variable that is pointing to a anonymous function that can be executed has a function, but the content of the function is changed with each assign, it even can be assign to non-anonymous functions

Clousures

Clousures are an application of the use anonymous functions that references a variable that is declared outside of the anonymous function, they have a firm like this

func name() func() int

What this will return is a anonymous function that is declared inside the function that we called, closures have also to proporty that they keep referencing the variables that weren't passed as parameters. Example

//This function will return a new anonymous function each time is called
func NewCounter() func() int{
   n := 0
   //The return function will increase the counter and return the value,
   return func() int{
       n += 1
       return n
   }
}
func main(){
   counter := NewCounter()
   fmt.println(counter())
   fmt.println(counter())
}

output

1
2

The function abode will create an anonymous function that will keep referring to the variable n and will increate the count each time is called it will increment the counter inside the function. Each newCounter that is created it will have his own n variable. This allow to create data isolation.

Closure function can also refer variable variables.


Modules

The use of modules is the new dependency management system added in version 1.11+, they work as a collection of packages that have a go.mod file in the root directory, this file contain all the path of packages, also contains all the external package requirements that need to be download, Go tool will check the local code and import all of this packages and add the latest version to the go.mod file if it have been not added.

The way to initialise a module is with he following command:

go mod init

Reflection

The package reflect can be imported to work with reflections, it allow to know the type, the name and set values. Since go is strongly typed reflection in go have 3 rules:

Reflection goes from interface value to reflection object.

The reflection package can get information from a interface such as type, name and value of a variable. This can be done with methods on the package, for example

func TypeOf(i interface{}) Type

This functions receive a empty interface, since this can hold any value, so when a argument is passed in the function first is stored in a empty interface.

Reflection goes from reflection object to interface value.

Reflection goes both ways and it can be transform an interface to a value, this can be done with the following method

func (v Value) Interface() interface{}

To modify a reflection object, the value must be settable.

Settability is the ability to modify the original values of the object, this is related to the addressability of the object and that if we have a copy of the value or the value himself.

One can berify the settability of an object with the method v.CanSet()

NOTE: All methods in reflection package assumes that the programmer knows that is doing and many functions and methods will panics when used incorrect.

More information: https://blog.golang.org/laws-of-reflection


Data handling

Files

The package os include most of the operation for file management. The package offers the file struct that allow to have a pointer to a file or a directory, this struct allow to open an close files, change owner, read or write to the file. It is highly recommended to add a defer with the close statement to make sure that the file is always close at the end. os.File offers two open methods File.Open(string) and File.OpenFile(string,int,FileMode), File.Open(String) will open the file in read only mode. The File structs have implemented already a Read and a Write method, those methods are declared in the package io as part of the interfaces io.Writer and io.Reader. This interface allows also to read from console or other media.

package main

import (
   "fmt"
   "os"
)

func main() {
   fd, err := os.Open("/Users/csantana/example")

   if err != nil {
       fmt.Println(err)
       return
   }

   defer fd.Close()
   names, err := fd.Readdirnames(5)

   if err != nil {
       fmt.Println(err)
       return
   }

   fmt.Println("Contents of directory " + fd.Name())

   for _, name := range names {
       childFd, err := os.Open(fd.Name() + "/" + name)
       buffer := make([]byte, 128)

       if err != nil {
           fmt.Println(err)
           return
       }

       defer childFd.Close()
       childFd.Read(buffer)
       fmt.Println("Text in file with name " + name)
       fmt.Println(string(buffer))
   }

}

This example will read up to 5 files in the directory and then will print the contents of the files

JSON

The package "encoding/JSON" has a lot of methods that allow an easy conversion from JSON-struct and struc-JSON, this can be done with the Marshal and UnMarshal methods. This important to mention that those methods respect the exported and non-exported variables, allowing to hide values when something is marshall to JSON

package main

import (
   "encoding/json"
   "fmt"
)

type personData struct {
   Name       string
   LastName   string
   Age        int
   Email      string
   creditCard string
}

func createPerson(name string, lastName string, age int, email string, cc string) *personData {
   return &personData{name, lastName, age, email, cc}
}

func main() {
   person := createPerson("Carlos", "Santana", 27, "csantana@luxoft.com", "XXXX XXXX XXXX XXXX")
   fmt.Println(person)
   b, err := json.Marshal(person)
   if err != nil {
       return
   }
   fmt.Println(string(b))
}

Output:

&{Carlos Santana 27 csantana@luxoft.com XXXX XXXX XXXX XXXX}
{"Name":"Carlos","LastName":"Santana","Age":27,"Email":"csantana@luxoft.com"}

In this example we can see that struct person data was converted to JSON, in the nex example I will do the inverse, will transform the json readed from a file to the struct.

package main

import (
   "encoding/json"
   "fmt"
   "os"
)

type personData struct {
   Name       string
   LastName   string
   Age        int
   Email      string
   creditCard string
}

func main() {
   var person personData
   fd, err := os.Open("jsonData")
   if err != nil {
       return
   }
   defer fd.Close()
   b := make([]byte, 114)
   fd.Read(b)
   fmt.Println("data on b :")
   fmt.Println(b)
   fmt.Println("String readeable of b")
   fmt.Println(string(b))
   err = json.Unmarshal(b, &person)
   if err != nil {
       fmt.Println(err)
       return
   }
   fmt.Println(person)
}

output:

data on b :
[10 123 34 78 97 109 101 34 58 34 67 97 114 108 111 115 34 44 34 76 97 115 116 78 97 109 101 34 58 34 83 97 110 116 97 110 97 34 44 34 65 103 101 34 58 50 55 44 3
4 69 109 97 105 108 34 58 34 99 115 97 110 116 97 110 97 64 108 117 120 111 102 116 46 99 111 109 34 44 34 99 114 101 100 105 116 67 97 114 100 34 58 34 88 88 88
88 32 88 88 88 88 32 88 88 88 88 32 88 88 88 88 34 125 10]
String readeable of b

{"Name":"Carlos","LastName":"Santana","Age":27,"Email":"csantana@luxoft.com","creditCard":"XXXX XXXX XXXX XXXX"}

{Carlos Santana 27 csantana@luxoft.com }

Tags can be added to the code so the name of the variables is not the same show in the JSON, those tags can be added in the declaration of the structs

type personData struct {
    Name       string `json:"name"`
    LastName   string `json:"lastName"`
    Age        int    `json:"years"`
    Email      string `json:"email"`
    creditCard string
}

https://blog.golang.org/json https://golang.org/pkg/os/ https://golang.org/pkg/io/


Command Line Interfaces

Flag package

This package has a lot utility methods that allows an easy integration with command line arguments. The following example will take two flags, one for a number and another for a string. The program will create a open the file with the name and will write numbers from 0 to the number.

package main

import (
   "flag"
   "fmt"
   "os"
   "strconv"
)

var nFlag = flag.Int("n", 0, "display message for n")
var fFlag = flag.String("f", "", "display message for f")

func main() {
   flag.Parse()
   if *nFlag == 0 {
       fmt.Println("Value of n cannot be 0")
       return
   }

   if *fFlag == "" {
       fmt.Println("Value of f cannot be empty")
       return
   }

   fd, err := os.OpenFile(*fFlag, os.O_RDWR|os.O_CREATE, 0755)
   if err != nil {
       fmt.Println(err)
       return
   }

   defer fd.Close()
   data := ""
   for i := 0; i <= *nFlag; i++ {
       data += strconv.Itoa(i) + " "
   }

   fd.Write([]byte(data))
}

The code is run like this:

$ go build hello.go
$ ./hello -n 10 -f something

And the file contains:

$ cat something
0 1 2 3 4 5 6 7 8 9 10

Viper & Cobra

Cobra and Viper are third party repos that allow an easy configuration for applications. Viper allows reading configuration files from different formats and translates and allows to set values, on the other hand Cobra is a powerful tool that helps with CLI applicartions. Some examples that use those tools are Hugo, kubernates and github CLI.

Use case

Vegeta is one CLI tool that is used perform testing on http servers, is totally created in go.

https://github.com/tsenart/vegeta https://golang.org/pkg/flag/ https://github.com/spf13/viper https://github.com/spf13/cobra


Web server

The package net/http allows to handle http requests, it allows to easy create and edit endpoint for a web server, but some the of the methods that offer are a basic and will require to write more code than using a web server framework.

package main
import (
   "fmt"
   "log"
   "net/http"
   "os"
)

// This is a handler that takes Requests and writes Responses
func index(res http.ResponseWriter, req *http.Request) {
   // Log just a bit of the request to Stdout
   log.Println(req.Method, req.URL, req.Proto)
   // Write a message to the ResponseWriter
   fmt.Fprintln(res, "Welcome to the Go server example.")
}
func main() {
   log.Println("Golang Server Example")
   // Set environment variable PORT to any available port (ie. 3000)
   if os.Getenv("PORT") == "" {
       log.Fatalln("Set the $PORT environment variable to run the server (ie. export PORT=3000)")
   }
   // Serve the root "/" path with the 'index' handler
   http.HandleFunc("/", index)
   // Log some more...
   log.Println("running server on port:", os.Getenv("PORT"))
   log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), nil))
}

Using this package will allow us to use all the HTTP methods, but it will need to a switch for each each the methods and one handle for each of the paths.

There are some frameworks that help simplification this issue:


Middleware

There are some third party libraries than use only the STD and can be used to create middleware, one of those is Negroni, and there some implementations of middleware.


Other libraries

Here is a list of possible libraries and projects that can be used Go

https://github.com/avelino/awesome-go

Those libraries include some of the following: Testing: