raulo5

Go Crash Course

Based on Learn Go Programming - Golang Tutorial for Beginners. Almost 7 hours long. All credits to freeCodeCamp, what I have below are just my own notes after watching the video, plus some other minor notes from official source (Golang doc).


Intro

Dowload Go binaries from Golang site

Set env vars: GOROOT and GOPATH

  • Avoid re-setting GOROOT if possible (just install Go on default paths)
  • Usual values:

Go binaries:

export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin

Go library sources and my own sources

export GOPATH=/home/raulbajales/golib
export PATH=$PATH:$GOPATH/bin
export GOPATH=/home/raulbajales/mygocode

Folder structure you need to create your own workspace:

$ cd /home/raulbajales/gocode
$ mkdir src
$ mkdir bin
$ mkdir pkg

IDE:

Hello World Go Edition

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}

Ways to create an executable:

Assuming we have a file named Main.go with package main containing a func main() then there are 2 ways to execute it:

  • Just run it:
$ cd /home/raulbajales/mygocode/src
$ go run Main.go
  • Build binary in /home/raulbajales/mygocode/src folder:
$ cd /home/raulbajales/mygocode/src
$ go build Main.go
$ ./Main

Variables

Declare then assign:

var i int
i = 10

Declare and assign at the same time:

var j int = 15

Declare and assign at the same time avoiding type (cannot be used at a package level):

i := 5

Declare at package level using a var block:

var (
	i int = 10
	j float32 = 5
	k String = "test"
)

Shadowing

Re-declaring a variable existent in current scope is not allowed, this is wrong (this won't compile as we are trying to re-declare variable i):

func main() {
	var i in = 10
	i := 15
}

You can't declare a variable twice in the same scope. But you can declare in inner sopes wit same names as in outer scopes. In the next example i is declared twice, but in different scopes. The variable with innermost scope takes precedence.

var i int = 10

func main() {
	fmt.Println(i)
	var i int = 20
	fmt.Println(i)
}

Output

10
20

Naming conventions

  • Lowercase variables are scoped to package or block scopes
// Here variable i has package scope
var i int = 10

func main() {
	// Here variable j has block scope
	var j int = 15
}
  • Uppercase variables are scoped to global scope (applies whenever your package gets imported)

Type casting

Needs to be done explicitly (Go won't do implicit data type conversion), by using the target type as a function (wrapping the variable to cast with the new type we want).

func main() {
	var i int = 10
	var j float32 = float32(i)
}

string is a particular case since this is handled internally as a stream of bytes. So to convert a number into a string we need to use strconv package

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var i int = 10
	var j string = strconv.Itoa(i)
	fmt.Println(j)
}

Output

10

But to convert a byte (an alias for int8) you can use string as a function, to get the ASCII char as a string

package main

import (
	"fmt"
)

var b byte = 65

func main() {
	fmt.Println(string(b))
}

Output

A

Primitives

  • Booleans
var b1 bool = true
var b2 bool = false
  • Numbers
// Signed Integers:
var i int = 10
// Specifying number of bytes:
var i8 int8 = 10
var i16 int16 = 10
var i32 int32 = 10
var i64 int64 = 10

// Unsigned Integers:
var u uint = 10
// Specifying number of bytes:
var u8 uint8 = 10 // "byte" is alias for "uint8"
var u16 uint16 = 10
var u32 uint32 = 10
var u64 uint64 = 10

// Floating point:
var f32 float32 = 10
var f64 float64 = 10

// Complex numbers:
var c64 complex64 = 1 + 2i
var c128 complex128 = 2 + 5i
// ... with complex functions:
var comp complex64 = complex(5, 12)
var re = real(c64) // Takes real part
var im = imag(c128) // Takes imaginary part

Go also has bitwise/bitshifting operators that only operates on integers:

&    bitwise AND           
|    bitwise OR            
^    bitwise XOR           
&^   bit clear (AND NOT) 
>>   Bit shift to the left
<<   Bit shift to the right
  • Text

A string is a collection of bytes, where a byte is type uint8 (where byte is also an alias for this type).

package main

import (
	"fmt"
)

func main() {
	// string (immutable value type)
	var s string = "Hello!"
	// Converting it to a slice of bytes
	var b []uint8 = []byte(s)

	// rune = alias for int32 to store UTF32 characters
	var r rune = '🤡'

	fmt.Println(b, r, string(r))
}

Output

[72 101 108 108 111 33] 129313 🤡

Constants

  • Scoping and naming conventions: Same as in variables
  • As in variables, they can be shadowed
  • Must to be calculable/assignable at compile time
  • Cannot change the value in runtime
const  myConst int = 10

Enumerated constants (iota)

Special value iota is set to the initial integer (0) and scoped to block. All subsequent values will be set incrementally. In the example below, (a == a2 && b == b2 && c == c2) is true

package main

import "fmt"

func main() {
	const (
		a = iota
		b
		c
	)

	const (
		a2 = iota
		b2
		c2
	)

	// Use _ to avoid using 0 as initial value:
	const (
		_ = iota
		a3
		b3
	)

	// An initial operation (even bitwise/bitshifting operators)
	// can be applied.
	// Here a4 is set to 6, and so on:
	const (
		_ = iota + 5
		a4
		b4
	)

	fmt.Printf("a = %v, b = %v, c = %v, a2 = %v, b2 = %v, c2 = %v, a3 = %v, b3 = %v, a4 = %v, b4 = %v", a, b, c, a2, b2, c2, a3, b3, a4, b4)
}

Output

a = 0, b = 1, c = 2, a2 = 0, b2 = 1, c2 = 2, a3 = 1, b3 = 2, a4 = 6, b4 = 7

Arrays

  • Mutable collections (with fixed size, zero based index) of items with same type.
  • Order of items is guaranteed
  • Its a value type meaning: whenever you assign an array to new variable you are actually copying the entire array creating a new one

Creating an array

package main

import "fmt"

func main() {
	// Declare and set initial values:
	var ar1 [3]int = [...]int{10, 15, 20}

	// Declare and set values after that:
	var ar2 [3]string
	ar2[0] = "Lisa"

	fmt.Printf("ar1 is %#v and ar2 is %#v", ar1, ar2)
}

Output

ar1 is [3]int{10, 15, 20} and ar2 is [3]string{"Lisa", "", ""}

Some array operations:

package main

import "fmt"

func main() {
	// Declare and set initial values:
	var ar1 [3]int = [...]int{10, 15, 20}

	// Get array length:
	l := len(ar1)

	// By assigning a new variable to an existing array
	// we are effectively copying the entire original array
	// Here ar2 is an entirely new array where its content
	// it's equal to the content in ar1
	var ar2 = ar1

	ar2[0] = 5 // Note that here only ar2 is modified

	fmt.Printf("ar1: %#v, ar1 length: %v, ar2: %#v", ar1, l, ar2)
}

Output

ar1: [3]int{10, 15, 20}, ar1 length: 3, ar2: [3]int{5, 15, 20}

Slices

  • Reference type
  • You don't need to know the size beforehand
  • Internally backed by an array

Creating a slice

package main

import "fmt"

func main() {
	// Declare and set initial values:
	var a1 []int = []int{10, 15, 20}

	// Slices are reference types, so assigning them to a new variable
	// does not copy the content, instead it maintains a reference
	// to the original one
	var a2 = a1 // Here a2 it's a reference to a1

	a2[0] = 5 // Note that here both arrays changes

	fmt.Printf("a1: %#v, a2: %#v", a1, a2)
}

Output

a1: []int{5, 15, 20}, a2: []int{5, 15, 20}

Some slice operations

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	b := a[:]   // slice of all elements
	c := a[3:]  // slice from 4th element to the end
	d := a[:6]  // slice from the beginning to 6th element
	e := a[3:6] // slice from 4th to 6th elements (1st number is inclusive, 2nd is exclusive)

	fmt.Printf("a = %v, a[:] = %v, a[3:] = %v, a[:6] = %v, a[3:6] = %v", a, b, c, d, e)
}

Output

a = [1 2 3 4 5 6 7 8 9], a[:] = [1 2 3 4 5 6 7 8 9], a[3:] = [4 5 6 7 8 9], a[:6] = [1 2 3 4 5 6], a[3:6] = [4 5 6]

Make function

package main

import "fmt"

func main() {
	// This will create a slice of 3 elements initialised to 0
	a := make([]int, 3)

	fmt.Printf("a = %#v", a)
}

Output

a = []int{0, 0, 0}

Append function

package main

import "fmt"

func main() {
	a := make([]int, 3)
	a = append(a, 1, 2, 3)

	fmt.Printf("a = %#v", a)
}

Output

a = []int{0, 0, 0, 1, 2, 3}

Concatenate slices

package main

import "fmt"

func main() {
	a := []int{1, 2, 3}
	b := []int{4, 5, 6}
	c := append(a, b...) // The ... works as a spread operator

	fmt.Printf("c = %#v", c)
}

Output

c = []int{1, 2, 3, 4, 5, 6}

Maps and Structs

Maps

  • Its a reference type.
  • Its mutable.
  • Key must be testable for equality.
  • Primitives and arrays are valid key types.
  • Entry order is not guaranteed.

Creating a map

package main

import "fmt"

func main() {
	// One-liner to create and set values:
	var m1 map[string]int = map[string]int{
		"key1": 1,
		"key2": 2,
	}

	// Just create an empty map
	var m2 map[string]int = make(map[string]int)

	fmt.Println(m1, m2)
}

Output

map[key1:1 key2:2] map[]

Inspecting and manipulating a map

package main

import "fmt"

func main() {
	var m1 map[string]int = map[string]int{
		"key1": 1,
		"key2": 2,
	}

	// Get a value for a key:
	k1 := m1["key1"]

	// Modify value for a key (or add a new key/value pair):
	m1["key1"] = 10
	m1["key3"] = 3

	// Delete an entry by providing a key:
	delete(m1, "key1")

	// Getting a value for a key fails silently if key is not found,
	// here is a way to know if key is found or not:
	v, keyExists := m1["nonExistentKey"] // here keyExists is false

	// Get amount of entries in a map:
	l := len(m1) // here l is 2

	fmt.Printf("Map m1 contains %v \n", m1)
	fmt.Printf("Value for key 'key1' is %v \n", k1)
	fmt.Printf("Value v for key 'nonExistentKey' is %v, and boolean keyExists for the same key is %v \n", v, keyExists)
	fmt.Printf("Length for map m1 is %v \n", l)
}

Output

Map m1 contains map[key2:2 key3:3] 
Value for key 'key1' is 1 
Value v for key 'nonExistentKey' is 0, and boolean keyExists for the same key is false 
Length for map m1 is 2 

Structs

  • Used to describe data
  • Its a collection type (same as array, slice and map)
  • Its a value type (not a reference type)
  • Usual variable naming conventions and scoping applies
    • Field names must be capitalized to be exposed globally

Declaring a struct type and creating an instance

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	aPerson := Person{
		Name: "John",
		Age:  20,
	}

	// One-liner to create and set an anonymous struct:
	aCar := struct{ brand string }{brand: "Toyota"}

	// Get value for a given field in a struct:
	name := aPerson.Name

	fmt.Printf("aPerson: %#v, aCar: %#v, name: %#v", aPerson, aCar, name)
}

Output

aPerson: main.Person{Name:"John", Age:20}, aCar: struct { brand string }{brand:"Toyota"}, name: "John"

Composition (thru Embedding)

Go does not support inheritance, but support composition thru embedding a struct properties into another struct. Applies to interfaces too.

package main

import "fmt"

type Animal struct {
	Name string
}

// Now here I embed Animal properties into Bird struct:
type Bird struct {
	Animal
	CanFly bool
}

func main() {
	// From now on any instance contains also properties
	// from Animal struct:
	b := Bird{}
	b.Name = "Duck"
	b.CanFly = true

	// But to declare and create a Bird in one step,
	// the internal Animal structure is needed:
	c := Bird{
		Animal: Animal{Name: "Duck"},
		CanFly: true,
	}

	fmt.Printf("b: %#v, c: %#v", b, c)
}

Output

b: main.Bird{Animal:main.Animal{Name:"Duck"}, CanFly:true}, c: main.Bird{Animal:main.Animal{Name:"Duck"}, CanFly:true}

Tags

Adds metadata to properties. Accessible via Go reflection package.

package main

import (
	"fmt"
	"reflect"
)

type Animal struct {
	Name string `required max:"100"`
}

func main() {
	t := reflect.TypeOf(Animal{})
	field, _ := t.FieldByName("Name")
	tag := field.Tag

	fmt.Println(tag)
}

Output

required max:"100"

Value vs Reference types

There are value types:

  • primitive types (int, double)
  • string
  • array
  • struct

And reference types:

  • slice
  • map

To avoid copying a value type when assigning, we can use & (address) prefix

a := [3]int{1,2,3} // This is an array
b := a // Here b is a new array, a copy of a 
c := &a // Here c is an array pointing to array a

String formatting and interpolation

  • For console output use fmt.Printf("Some string here...", arg1, arg2, ..., argn)
  • For string creation use fmt.Sprintf ("Some string here...", arg1, arg2, ..., argn)
  • A placeholder in a string literal has the forma %x where x is the formatting verb

Default format: "%v"

package main

import (
	"fmt"
)

func main() {
	fmt.Printf("%v \n", true)           // boolean
	fmt.Printf("%v \n", 123)            // integer
	fmt.Printf("%v \n", 123.456)        // floating point
	fmt.Printf("%v \n", (3 + 7i))       // complex
	fmt.Printf("%v \n", "Hello")        // string
	fmt.Printf("%v \n", []int{1, 2, 3}) // slice
	fmt.Printf("%v \n", map[string]int{
		"one": 1,
		"two": 2,
	})
	fmt.Printf("%v \n", struct {
		name string
		age  int
	}{"John", 26}) // structs
}

Output

true 
123 
123.456 
(3+7i) 
Hello 
[1 2 3] 
map[one:1 two:2] 
{John 26} 

Code format: "%#v"

package main

import (
	"fmt"
)

func main() {
	fmt.Printf("%#v \n", true)           // boolean
	fmt.Printf("%#v \n", 123)            // integer
	fmt.Printf("%#v \n", 123.456)        // floating point
	fmt.Printf("%#v \n", (3 + 7i))       // complex
	fmt.Printf("%#v \n", "Hello")        // string
	fmt.Printf("%#v \n", []int{1, 2, 3}) // slice
	fmt.Printf("%#v \n", map[string]int{
		"one": 1,
		"two": 2,
	})
	fmt.Printf("%#v \n", struct {
		name string
		age  int
	}{"John", 26}) // structs
}

Output

true 
123 
123.456 
(3+7i) 
"Hello" 
[]int{1, 2, 3} 
map[string]int{"one":1, "two":2} 
struct { name string; age int }{name:"John", age:26}

Default format with field name flag (applies to structs): "%#v"

package main

import (
	"fmt"
)

func main() {
	s := struct {
		name string
		age  int
	}{"John", 26}
	fmt.Printf("%+v", s)
}

Output

{name:John age:26}

Type format: "%T"

  • Shows the data type of a variable or value
package main

import (
	"fmt"
)

func main() {
	fmt.Printf("%#T \n", true)           // boolean
	fmt.Printf("%#T \n", 123)            // integer
	fmt.Printf("%#T \n", 123.456)        // floating point
	fmt.Printf("%#T \n", (3 + 7i))       // complex
	fmt.Printf("%#T \n", "Hello")        // string
	fmt.Printf("%#T \n", []int{1, 2, 3}) // slice
	fmt.Printf("%#T \n", map[string]int{
		"one": 1,
		"two": 2,
	})
	fmt.Printf("%#T \n", struct {
		name string
		age  int
	}{"John", 26}) // structs
}

Output

bool 
int 
float64 
complex128 
string 
[]int 
map[string]int 
struct { name string; age int } 

Decimal ("%d"), Floating point ("%w.pf"), Binary ("%d"), Hexa("%x") and Octa ("%o") formats

  • For Floating Point format ("%w.pf") w is width and p is precision
import (
	"fmt"
)

func main() {
	fmt.Printf("%v \n", 30)        // Decimal
	fmt.Printf("%v \n", 0xFF)      // Decimal (from an hexa)
	fmt.Printf("%5.3f \n", 123.45) // Floating point
	fmt.Printf("%b \n", 9)         // Binary (from decimal 9)
	fmt.Printf("%x \n", 124)       // Hexa (from decimal 124)
	fmt.Printf("%o \n", 9)         // Octa (from decimal 9)
}

Output

30 
255 
123.450 
1001 
7c 
11 

Control Flow

if statement

package main

import "fmt"

func main() {
	// Reguar syntax:
	if true {
		// do something...
	} else {
		// won't happen...
	}

	// Initialiser syntax:
	m := map[string]int{
		"key": 1,
	}
	if v, ok := m["key"]; ok {
		// do something with value v if ok is true (if key exists in map)...
		// note that v and ok are scoped to this block
		fmt.Println(v)
	} else {
		fmt.Println("Key 'key' does not exist in map m")
	}
	// v and ok are not accessible here...
}

Output

1

Boolean comparison operators

<    less than
>    greater than
==   equal
!=   not equal
<=   less or equal than
>=   greater or equal than

Boolean logical operators

&&    and
||    or
!     not

switch statemet

  • Overlapping cases are not allowed (causes compile time error) with tag syntax

Tag syntax

package main

import "fmt"

func main() {
	switch 2 {
	case 1, 3:
		fmt.Println("Alo")
	case 4:
		fmt.Println("Ola")
	default:
		fmt.Println("Not sure...")
	}
}

Output

not sure...

Initialiser syntax

package main

import "fmt"

func main() {
	// Note that i has block scope
	switch i := 2 + 3; i {
	case 10:
		fmt.Println("Ten")
	default:
		fmt.Println("Not sure...")
	}
	// i is not accessible here
}

Output

Not sure...

Tagless syntax (cases can overlap)

package main

import "fmt"

func main() {
	// Note that you can use fallthrough to avoid implicit break
	// Also you can use break explicitly
	i := 10
	switch {
	case i > 5:
		fmt.Println("Its more than five")
		fallthrough
	case i > 7:
		fmt.Println("Its more than seven")
	default:
		fmt.Println("... not sure what it is")
	}
}

Output

Its more than five
Its more than seven
package main

import "fmt"

func main() {
	var i interface{} = 1 // We may not know the type of i if received as parameter
	switch i.(type) {
	case int:
		fmt.Println("int")
	case string:
		fmt.Println("string")
	default:
		fmt.Println("Not sure...")
	}
}

Output

int

Looping

Regular syntax

package main

import "fmt"

func main() {
	for i := 0; i < 5; i++ {
		// i is scoped to block and only exists here...
		fmt.Println(i)
	}
}

Output

0
1
2
3
4

Initialising and incrementing 2 variables

package main

import "fmt"

func main() {
	for i, j := 0, 9; i < 5; i, j = i+1, j-1 {
		// i and j are scoped to block and only exists here...
		fmt.Println(i, j)
	}
}

Output

0 9
1 8
2 7
3 6
4 5

"while" loop with explicit condition

package main

import "fmt"

func main() {
	i := 0
	for i < 5 {
		i++
		fmt.Println(i)
	}
}

Output

1
2
3
4
5

"while" loop without explicit condition (using break instead)

package main

import "fmt"

func main() {
	// Note that you can also use continue to avoid executing the rest
	// of the iteration block
	i := 0
	for {
		i++
		if i == 1 {
			continue // Wont execute the rest of the code in this for block
		}
		if i == 5 {
			break // Leaves the closest loop
		}
		fmt.Println(i)
	}
}

Output

2
3
4

Looping into a collection using range

package main

import "fmt"

var m map[string]int = map[string]int{
	"key": 1,
}
var a [3]int = [...]int{1, 2, 3}

func main() {
	fmt.Println("Looping a map (key and value):")
	for k, v := range m {
		fmt.Println(k, v) // k is key and v is value in map m
	}

	fmt.Println("Looping a map (only key):")
	for k := range m {
		fmt.Println(k) // k is key in map m
	}

	fmt.Println("Looping a map (only value):")
	for _, v := range m {
		fmt.Println(v) // v is value in map m
	}

	fmt.Println("Looping an array (works on slices too):")
	for k, v := range a {
		fmt.Println(k, v) // k is the index and v is the value in array a
	}

	fmt.Println("Looping a string (thru its bytes):")
	for k, v := range "John Doe" {
		fmt.Println(k, v, string(v)) // k is the index and v is the byte value
	}
}

Output

Looping a map (key and value):
key 1
Looping a map (only key):
key
Looping a map (only value):
1
Looping an array (works on slices too):
0 1
1 2
2 3
Looping a string (thru its bytes):
0 74 J
1 111 o
2 104 h
3 110 n
4 32  
5 68 D
6 111 o
7 101 e 

Defer / Panic / Recover

Defer

  • Will defer the execution of a function call to the end of the code flow.
  • Scoped to current function, in correlation to return and panic statements (or the end of the function when none of these statements are reached).
  • Multiple defer will be executed in LIFO order.
  • Useful to close resources.
  • The usual pattern when working with resources is:
    1. Open resource
    2. Check for errors
    3. Defer resource close
  • Avoid the above pattern when working with resources inside a loop (just close the resource as quickly as you finished using it)
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

func main() {
	// 1. Open resource:
	res, err := http.Get("http://www.google.com/robots.txt")
	// 2. Check for errors:
	if err != nil {
		log.Fatal(err)
	}
	// 3. Defer resource close:
	defer res.Body.Close()

	// ... here continue processing the resource:
	robots, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("%s", robots)
}

Output

API server listening at: 127.0.0.1:39016
User-agent: *
Disallow: /search
# AdsBot
User-agent: AdsBot-Google
Disallow: /maps/api/js/
Allow: /maps/api/js
Disallow: /maps/api/place/js/
Disallow: /maps/api/staticmap
Disallow: /maps/api/streetview
...
...
...
# Certain social media sites are whitelisted to allow crawlers to access page markup when links to google.com/imgres* are shared. To learn more, please contact [email protected].
User-agent: Twitterbot
Allow: /imgres

User-agent: facebookexternalhit
Allow: /imgres

Sitemap: https://www.google.com/sitemap.xml

Defer will eagerly evaluate the values on the deferred statement, as shown here:

package main

import (
	"fmt"
)

func main() {
	a := 1
	defer fmt.Println(a)
	a = 2
}

Output

1

Panic

  • Generated by the runtime whenever the program causes an exception
  • Can be invoked by panic("some error message")
  • Explicit panic in code always happens after all defer gets excecuted

Simple webserver example, panicking whenever the server cannot start (open browser in http://localhost:8080/)

package main

import "net/http"

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello go!"))
	})
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err.Error())
	}
}

Recover

  • Helps recovering from a panic call
  • Whenever an invoked function panics and then recover, the invoker will continue its excecution
package main

import (
	"fmt"
)

func main() {
	fmt.Println("start")
	panicker()
	fmt.Println("end")
}

func panicker() {
	fmt.Println("...about to panic!")
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			// you can re-panic here if error is not recoverable:
			// panic(err)
		}
	}()
	panic("something bad happened")
	fmt.Println("this will not be printed")
}

Output - Note that "end" is printed out but "this will not be printed" is not

start
...about to panic!
something bad happened
end

Pointers

  • To define a pointer a prefix * should prefix the actual type
  • The value for a variable of pointer type is a reference
  • To assign a reference to a variable of pointer type we need to use prefix & on the target variable
  • To de-reference a pointer variable to get the memory address the prefix * should be used
  • Pointer arithmetic (as in C language) is not allowed
  • Reference types (slice, map) handles underlying references
  • Structs are automatically de-referenced (no need to use * prefix)
package main

import (
	"fmt"
)

func main() {
	var a int = 42
	var b *int = &a // here variable b is a pointer to variable a
	*b = 10         // here variable a is being set the value 10
	var c int = *b  // here variable c is a new variable set to value 10
	fmt.Println(a, b, c)
}

Output

10 0xc0000140a0 10

Functions

  • Function main in package main is the entry point for a Go program
  • Usual naming convention applies for scoping
  • Functions are types too
func someFunc(a int, b String) {
	// ...
}

func someOtherFunc(a, b, c int) {
	// a, b and c are of type int
}

Variadic parameters can be handled as a slice, and must be set as the final paramater

package main

import (
	"fmt"
)

func main() {
	total := sum("the sum is", 1, 2, 3, 4)
	fmt.Println(total)
}

func sum(msg string, values ...int) int {
	result := 0
	for v := range values {
		result += v
	}
	fmt.Println(msg, result)
	return result
}

Output

the sum is 6
6

Returning named variables

package main

import (
	"fmt"
)

func main() {
	total := sum(1, 2, 3, 4)
	fmt.Println(total)
}

func sum(values ...int) (result int) {
	for v := range values {
		result += v
	}
	return
}

Output

6

Go can return multiple values, the types needs to be set at the end of the function signature as a comma separated list

package main

import (
	"fmt"
)

func main() {
	if d, err := divide(5.0, 0.0); err != nil {
		fmt.Println(err)
		return
	} else {
		fmt.Println(d)
	}
}

func divide(a, b float64) (float64, error) {
	if b == 0.0 {
		return 0.0, fmt.Errorf("Cannot divide by zero")
	}
	return a / b, nil
}

Output

Cannot divide by zero

func is also a type.

package main

import (
	"fmt"
)

var f func(float64, float64) (float64, error) = func(a, b float64) (float64, error) {
	if b == 0.0 {
		return 0.0, fmt.Errorf("Cannot divide by zero")
	}
	return a / b, nil
}

func main() {
	for i := 0; i < 3; i++ {
		fmt.Println(f(100.0, float64(i)))
	}
}

Output

0 Cannot divide by zero
100 <nil>
50 <nil>

Functions can also be added in the context of a struct (or any other type), in this scenario these functions are called methods

package main

import (
	"fmt"
)

func main() {
	g := &greeter{
		greeting: "Hello",
		name:     "Go!",
	}
	fmt.Println(g.greet())
}

type greeter struct {
	greeting string
	name     string
}

func (g *greeter) greet() string {
	return fmt.Sprintf("%v %v", g.greeting, g.name)
}

Output

Hello Go!

Interfaces

  • Used to describe behavior (in contrast to struct which is used to describe data)
  • Implementation for an interface goes into a struct (mostly, but can go into any other type too)
  • The struct only needs to implement methods in interface using the same signature (implicit implementation)
  • You can compose interfaces thru embedding (as in structs)
  • A factory method is the usual way to create new instances of structs implementing an interface
package main

import (
	"fmt"
)

func main() {
	// note that w is type Writer
	var w Writer = NewConsoleWriter()
	w.Write([]byte("Hello!"))
}

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

type ConsoleWriter struct {
	prefix string
}

func (cw *ConsoleWriter) Write(data []byte) (int, error) {
	n, err := fmt.Println(cw.prefix, string(data))
	return n, err
}

func NewConsoleWriter() *ConsoleWriter {
	return &ConsoleWriter{
		prefix: "Go Says:",
	}
}

Output

Go Says: Hello!

The empty interface

  • The empty interface interface{} is a very common type in libraries, but should be seen as an intermediate step as you may need to convert it (by using the notation variable.(type)) to effectively use its implementation.
  • You can safely check if a pointer to an empty interface "can be seen" as a particular interface (this can be used to avoid panicking when conversion fails), this is called type assertion
package main

import (
	"fmt"
)

func main() {
	var i interface{} = 0

	// You can check type with a switch/case:
	switch i.(type) {
	case int:
		fmt.Println("It's an int")
	case float64:
		fmt.Println("It's a float64")
	default:
		fmt.Println("Not sure what it is...")
	}

	// Or with an if:
	if tmp, ok := i.(int); ok {
		fmt.Printf("%v it's an int", tmp)
	} else {
		fmt.Println("Not an int...")
	}
}

Output

It's an int
0 it's an int

Best practices on Interfaces

These are useful when implementing a package to be used by others in the team.

  • Better many small interfaces
  • Export concrete types (structs) instead of interfaces, unless the library consumer need to use these interfaces explicitly
  • Design functions receiving interfaces as parameter whenever possible

Goroutines

  • Enables concurrent / synchronization / parallelism.
  • Uses the concept of green thread (meaning, in most cases a goroutine won't use a real thread)
  • Use sync.WaitGroup to synchronize goroutines
  • Use sync.RWMutex to synchronize access to shared resources, allowing single goroutine to write but multiple goroutines to read
  • Use runtime.GOMAXPROCS to fine tune (in PROD) the number of green threads, and force it to runtime.GOMAXPROCS(1) to reveal race condition issues while developing

Waitgroups

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}
var m = sync.RWMutex{}

var counter int = 0

func main() {
	wg.Add(3) // Will work with 3 goroutines

	// Each of the following goroutines will mark end of processing with wg.Done()

	go func() {
		m.Lock() // Lock for write (allow only this goroutine to write)
		counter++
		m.Unlock() // Unlock
		fmt.Println("Let's Go!")
		wg.Done()
	}()

	go func() {
		m.RLock() // Lock for read (no goroutine can write, but all can read)
		fmt.Println("Ho!", counter)
		m.RUnlock() // Unlock
		wg.Done()
	}()

	go func() {
		m.Lock() // Lock for write (allow only this goroutine to write)
		counter++
		m.Unlock() // Unlock
		fmt.Println("Hey!")
		wg.Done()
	}()

	wg.Wait() // Wait for all 3 goroutines to finish
}

Output

Hey!
Ho! 1
Let's Go!

Channels

  • Designed to synchronize data flow between multiple goroutines
  • A channel is strongly typed (a single channel will handle data of a single type)
  • A channel can be buffered, allowing sender and receiver to operate at different rates
  • A range loop can be used to READ from a channel
  • A channel can be set to send struct{} type, this is a signal-only channel, useful to send ACK signals
package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{}

func main() {
	// Buffered channel: up to 50 messages can be sent/received at a time in this channel
	ch := make(chan int, 50)
	wg.Add(2)

	// Signature says: This goroutine READs from channel ch
	go func(ch <-chan int) {
		// You can read all messages in channel until it gets closed with a for range:
		for i := range ch {
			fmt.Println(i)
		}
		// And you can also try to get a single message in a safe way:
		if j, ok := <-ch; ok {
			fmt.Println(j)
		} else {
			fmt.Println("Cannot read from channel, seems closed")
		}
		wg.Done()
	}(ch)

	// Signature says: This goroutine WRITEs to channel ch
	go func(ch chan<- int) {
		j := 42
		ch <- j   // 42 (value of j here) will be sent to channel ch
		ch <- 30  // Another plain int value sent here to channel ch
		close(ch) // Needed to signal readers that no more messages will be sent
		wg.Done()
	}(ch)

	wg.Wait()
}

Output

42
30
Cannot read from channel, seems closed

Select statement

  • Similar to switch/case but applies to channels
  • Will match the case for the channel that received a message
  • It's a blocking operation, unless you use default clause
package main

import (
	"fmt"
)

var writerCh = make(chan string, 50)
var doneCh = make(chan struct{}) // This a signal-only channel

func main() {
	go consoleWriter() // Spawn the goroutine
	writerCh <- "Hello"
	writerCh <- "Go!"
	doneCh <- struct{}{} // Just sending an empty struct as a signal
}

func consoleWriter() {
	// This loop will repeat until break condition (it's a blocking loop)
	for {
		select {
		case entry := <-writerCh:
			fmt.Println(entry)
		case <-doneCh:
			break
		// Make this select non-blocking by using a `default` case here
		}
	}
}

Output

Hello
Go!

Testing

  • Usually a test goes in the same folder as the go file to be tested having same file name but with suffix _test (as in Sample.go vs Sample_test.go)
  • To run automated tests use go test which will execute all functions in the form of func TestXxx(*testing.T)
  • Use table tests (using structs as the source of the data) to provide multiple data for a single test

Example of unit tests including table tests

Sample.go

package main

// Function to be tested:
func IntMin(a, b int) int {
	if a < b {
		return a
	}
	return b
}

Sample_test.go

package main

import (
	"fmt"
	"testing"
)

// Sample unit test:
func TestIntMin(t *testing.T) {
	expectede := -2
	actual := IntMin(2, -2)
	if expectede != actual {
		t.Errorf("Expected %v but got %v", expectede, actual)
	}
}

// Sample table test:
func TestIntMin_Table(t *testing.T) {
	var tests = []struct {
		a, b   int
		result int
	}{
		{0, 1, 0},
		{10, 1, 1},
		{-2, -11, -11},
		{-20, -11, -20},
	}
	for _, v := range tests {
		name := fmt.Sprintf("%v,%v", v.a, v.b)
		t.Run(name, func(t *testing.T) {
			expected := v.result
			actual := IntMin(v.a, v.b)
			if expected != actual {
				t.Errorf("Expected %v but got %v", expected, actual)
			}
		})
	}
}

Output (after running $ go test -v for verbosity)

$ go test -v

=== RUN   TestIntMin
--- PASS: TestIntMin (0.00s)
=== RUN   TestIntMin_Table
=== RUN   TestIntMin_Table/0,1
=== RUN   TestIntMin_Table/10,1
=== RUN   TestIntMin_Table/-2,-11
=== RUN   TestIntMin_Table/-20,-11
--- PASS: TestIntMin_Table (0.00s)
    --- PASS: TestIntMin_Table/0,1 (0.00s)
    --- PASS: TestIntMin_Table/10,1 (0.00s)
    --- PASS: TestIntMin_Table/-2,-11 (0.00s)
    --- PASS: TestIntMin_Table/-20,-11 (0.00s)
PASS
ok  	_/Users/raulbajales/mygocode/src

Error handling

  • The usual pattern for a function that may cause a failure in runtime is:
    • The return type should be a tuple where the first element if the success value, and the second element is either an error or a bool
    • In success case: The first element of the tuple is set to the success value and the second element is set to nil or true
    • In failure case: The first element of the tuple is set to a default value and the second element is set to error or false
  • If you need to generate an error you can use errors.New(msg string) or fmt.Errorf(msg string, args ...interface{})
  • You can build your set or hierarchy of domain specific error types (just need to satisfy the predefined error interface), then whenever you need to handle the errors you can use type assertion for custom error handling

Example 1, showing:

  • Return tuple (success, error)
  • Handle success and error case
  • Generate an error with custom message
package main

import (
	"fmt"
)

func main() {
	// Get tuple to check for success or failure
	if d, err := divide(5.0, 0.0); err != nil {
		fmt.Println(err)
		return
	} else {
		fmt.Println(d)
	}
}

func divide(a, b float64) (float64, error) {
	if b == 0.0 {
		// Failure case:
		return 0.0, fmt.Errorf("Cannot divide %v by zero", a)
	}
	// Success case:
	return a / b, nil
}

Output

Cannot divide 5 by zero

Example 2, showing same case but with return type (success, bool):

package main

import (
	"fmt"
)

func main() {
	// Get tuple to check for success or failure
	if d, ok := divide(5.0, 0.0); ok {
		fmt.Println(d)
	} else {
		fmt.Println("Cannot divide by zero")
	}
}

func divide(a, b float64) (float64, bool) {
	if b == 0.0 {
		// Failure case:
		return 0.0, false
	}
	// Success case:
	return a / b, true
}

Output

Cannot divide zero

Next topics


That's it (: