Go Language

From bibbleWiki
Jump to navigation Jump to search

Introduction

Characteristics

Google was using c++, java and python.

  • c++ high performance, type safety, slow compile, complex
  • java rapid compile, type safety, complicated eco system
  • python easy to use, lack of type safety, slow

So Go was created which had

  • Fast compilation
  • Fully compiled, better performance
  • Strongly typed
  • Concurrent by default, threaded
  • Garbage collected
  • Simplicity as a core value, for example Garbage collected, Strongly typed.

Whats Go Good At?

Good

  • Go is easy to learn
  • Easy concurrent programming with goroutines and channels
  • Great standard library
  • Go is performant
  • Language defined source code format
  • Standardized test framework
  • Go programs are great for operations
  • Defer statement, to avoid forgetting to clean up
  • New types

Bad

  • Go ignored advances in modern language design
  • Interfaces are structural types
  • Interface methods don't support default implementations
  • No enumerations
  • The := / var dilemma
  • Zero values that panic
  • Go doesn't have exceptions. Oh wait... it does!

Ugly

  • The dependency management nightmare
  • Mutability is hardcoded in the language
  • Slice gotchas
  • Mutability and channels: race conditions made easy
  • Noisy error management
  • Nil interface values
  • Struct field tags: runtime DSL in a string
  • No generics... at least not for you
  • Go has few data structures beyond slice and map
  • go generate: ok-ish, but...

Hello World

package main

import (
	"fmt"
)

// Comments are here
func main() {
	fmt.Println("Hello World")
}

Modules

Modules are files for controlling a project. To create a module

go mod init github.com/bibble235/webservice

To run our code

go run github.com/bibble235/webservice

Data Types

Boolean types

They are boolean types and consists of the two predefined constants: (a) true (b) false

Numeric types

They are again arithmetic types and they represents a) integer types or b) floating point values throughout the program.

Integer types

  • uint8 Unsigned 8-bit integers (0 to 255)
  • uint16 Unsigned 16-bit integers (0 to 65535)
  • uint32 Unsigned 32-bit integers (0 to 4294967295)
  • uint64 Unsigned 64-bit integers (0 to 18446744073709551615)
  • int8 Signed 8-bit integers (-128 to 127)
  • int16 Signed 16-bit integers (-32768 to 32767)
  • int32 Signed 32-bit integers (-2147483648 to 2147483647)
  • int64 Signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

Floating types

  • float32 IEEE-754 32-bit floating-point numbers
  • float64 IEEE-754 64-bit floating-point numbers
  • complex64 Complex numbers with float32 real and imaginary parts
  • complex128 Complex numbers with float64 real and imaginary parts

Other Numeric types

  • fbyte same as uint8
  • frune same as int32
  • fuint 32 or 64 bits
  • fint same size as uint
  • uintptr an unsigned integer to store the uninterpreted bits of a pointer value

String types

A string type represents the set of string values. Its value is a sequence of bytes. Strings are immutable types that is once created, it is not possible to change the contents of a string. The predeclared string type is string.

Derived types

They include (a) Pointer types, (b) Array types, (c) Structure types, (d) Union types and (e) Function types f) Slice types g) Interface types h) Map types i) Channel Types

Declaring

There are 3 way to declare variables

// explicit
var i int

// explicit and assign
var i = 42

// Implicit
firstname := "Iain"

Pointers

We can create pointers with the "*"

var firstName *string // Nil

Assigning pointers is the same as c++ however it does not support pointer arithmathic

var firstName *string = new(string) // Nil
*firstName = "Arthur"
fmt.Println(*firstName)

Dereferencing is the same as c++ too.

var firstName *string // Nil

Assigning pointers is the same as c++ however it does not support pointer arithmathic

firstName := "Arthur"
ptr := &firstName

Constants

Introduction

Constant are available in go.

const pi = 3.1415

// types are not explicit so
const c = 3
fmt.Println(c + 1.2)
// Will work because there is not type and 1.2 can be added to c if c is deemed a float at runtime.

Iota

We can create consts like so

const (
    fred1 := "fred1" 
    fred2 := 2 
)

Using iota we can we can increment the values using an expression. e.g.

const (
    fred1 = iota
    fred2 = iota
)

// Outputs 0, 1

But we can put expression in this.

const (
    fred1 = iota + 6
    fred2 = 2 << itoa
)
// Outputs 6, 4

Omitting the expression after first declaration will mean the first expression is repeated. Iota is scoped to the block of code

const (
    fred1 = iota + 6
    fred2
)
// Outputs 6, 7

Collections

Arrays

With go all the elements must have the same type. The type is declared at the end. They are zero based. I.E. the first array is at index 0. You cannot increase the size dynamically.

var arr [3]int 
arr[0] = 1
arr[0] = 2
arr[0] = 3

// Better with initializer
arr2 := [3]int {1,2,3}

Slices

Introduction

The slice is a dynamic type which can be derived/linked from an array

arr2 := [3]int {1,2,3}

slice := arr[:]

arr[1] = 42
slice[2] = 27

// output [1,42,27]

Appending

We can create a slice from an anonymous array and append

slice := []int {1,2,3}

slice = append(slice, 4,42,27)

// output [1,2,3,4,42,27]

Ranges

We can slice a slice

slice := []int {1,2,3}

slice = append(slice, 4,42,27)

s2 := slice[1:] // Element 1-end
s3 := slice[:2] // Element 0-2 Exclusive 0,1 not 2
s4 := slice[1:2] // Element 1-2 Exclusive 1 not 2

Map

Maps are like dictionaries. They associate a key with a value in a collection

m := map[string]int {"foo":42,"foo2":42}
fmt.Println(m["foo"])

// Deleting uses delete function
delete(m, "foo")

Structs

Introduction

Structs field definitions are fixed at compile time like c++ structs. They are defined using the word type. The fields are initialised to their zero value

// Define
type use struct {
    ID int
    FirstName string
    LastName string
}

// Declare
var myUser user

// Initialisation like a dictionary
// Add comma on last line for multi-line
u2 := user { 
    ID:1,
    FirstName: "Iain",
    LastName: "Wiseman",
}

// Assign
myUser.ID = 1
myUser.FirstName = "Iain"
myUser.LastName = "Wiseman"

Functions

Basics

No surprises here except the symbol name is first

func test(inPort int, inNumberOfReries) bool {

    return true
}
isDone = test(1701, 3)

Multiple Parameters

We can pass multiple parameters to as function and similar we can return multiple parameters for GO it is typical to return the error type back. Note consecutive input parameters same type can be comma delimited

func test(inParm1, inParam2 int, inNumberOfReries) (int, error) {

    return 300, nil
}

Returning error

In GO it is typical to return a result and an error object. Here is the divide by zero in GO.

func divide(inParm1, inParam2 float64) (float64, error) {
    if p2 == 0 {
        return math.NaN(), errors.New("Cannot divide by zero")
    return (p1/p2, nil)
}

Variadic Parameters

Easy pezzy lemon squezzy. Just gets passed as an array. Like c# this must be the last parameter.

func sum(values ...float64) float64 {
    total := 0.0
    for _, value := range values {
        total += value
    }
    return total
}

Naming Return Variables

If you name the return parameters you can have a return with no parameters.

func test(p1, p2 float64) (answer float64, err error) {
     answer = p1 / p2
     return
}

Methods

Methods are just function on structs. Note the pointer, this is called a pointer receiver.

type Person struct {
    ID id
    Name string
}

func (p* Person) Test(param1, parm2 string) (int, test) {

 return (20, nil)
}

Public and Private

Functions a can be made public by capitalising the first character.

func fooPrivate() bool {
    return true
}

func FooPublic() bool {
    return true
}

Anonymous Functions

We can create these within a function

func Foo() bool {
    func() {
        println("Hello World");
    }()
}

We can assign and invoke too

func Foo() bool {
    x := func(name string) {
        println(name);
    }

    x("Function 1");
    x("Function 2");
}

Returning a Functions in a Function

This allows you to return a function (like maybe delegates)

func main() {
    addExpression := MathExpression()
    println(addExpression(2,3))

}

func MathExpression() func(float64,float64) float64 {

    return (f1, f2 float64) float64 {
        return f1 + f2
    }
}

Passing a Functions as an Argument

This allows you to pass function to a function

func foo(f1, f2 float64, mathExpr func(float64,float64) float64) float64 {
    return 2 * mathExpr(f1,f2
}

Stateful Functions

This concept allow the function to hold state. The function retains the value of x for each subsequent call.

function powerOfTwo() func() int64
{
    x := 1 
    return func() int64 {
       x += 1 
       return int64(math.Pow(x,2))
    }
}

p2 := powerOfTwo()
value := p2()
value := p2()   
value := p2()   
// 4
// 9
// 16

Resource Management (using)

GO has the defer function for this. This is called at the end of the function. These functions are put on a stack and executed in last in first out order.

func ReadFile() error {
    var . io.ReadCloser = &IainsReader()
    defer func() {
       _ = r.Close()
    }()

    for {
        value, err := r.Read([]bytes("Fred was here"))
        if err == io.EOF {
            break;
        } else if err != nil {
            return err
        }
    }
    return nil
}

Types

Introduction

We can either have alias or type definition. These are the same as typedef and class in C++

Type Alias

Type alias allow you to typedef a type but you cannot extend an alias.

type iains_type = string

Type Definition

This makes a thing is own type

type iains_type string

Embedding Type struct

In GO you can embed a type struct into another type.

type Name struct {
   first string
   last string
}

type Person struct {
    Name
    twitterHandler TwitterHandler
}

Embedding Type interfaces

In GO you can embed a type interface into another type.

type Identifiable interface {
   ID() string
}

type socialSecurityNumber string 

func NewSocialSecurityNumber(value string) Identifier {
    return socialSecurityNumber(value)
}

func (ssn socialSecurityNumber) ID() string {
   return string(ssn)
}

type Person struct {
   Name, 
   twitterHandler TwitterHandler,
   identifiable
}

func NewPerson(firstName, lastName string, identifiable Identifiable) Person {
    return Person{
         Name: Name {
            first: firstName,
            last: lastName,
         },
         Identifiable: identifiable
    }
}

Type Equality

Equality with Basic structs

Standard approach for structs

name1 : = Name{First: "James", Last: "Wilson"}
name2 : = Name{First: "James", Last: "Wilson"}
if name1 == name2 {
   // Success 
}

type Name struct {
   First string
   Last string
}

Equality with Non Basic Structs

Adding a other types will not be the case. e.g. func or map

type Name struct {
   First string
   Last string
   foo func()
}

type Name struct {
   First string
   Last string
   Middle map[string]string
}

Equality with Empty Types

We can compare to empty types

if name1 == (Name{}) {
   // Blah blah
}

Equality Overloads

We can overload with our own function

func (n Name) Equals(otherName Name) bool {
   return n.First == other.First && n.Last == other.Last
}

Equality with Interfaces

This works based on the type which is returned within the method being compared

ssn :== organization.NewSocialSecurityNumber("123-123")
eu :== organization.NewEuroNumber("123","France")
eu2:== organization.NewEuroNumber("123","France")

if ssn == eu {
   fmt.println("Fails but compiles")
}

if eu == eu2 {
   fmt.println("Success")
}

Classes (Or not)

Introduction

package controllers

import (
	"net/http"
	"regexp"
)

// Class Attributes
type userController struct {
	userIDPattern *regexp.Regexp
}

// Function for class
func (uc userController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello from the User Controller!"))
}

// Constructor with default arguments
func newUserController() *userController {
	return &userController{
		userIDPattern: regexp.MustCompile(`^/users/(\d+)/?`),
	}
}

Fields

By making the fields lowercase in a struct they become private. We can initialize them using the constructor method usually new<typename>(param1, param2 string) typename

func NewPerson(firstName, lastName string) Person {
    return Person {
         firstName: firstName,
         lastName: lastName,
    }
}

Getters And Setters

The course advice to have pointer arguments to functions for the object. So example of getters and setters for the above is.

func (p *Person) SetTwitterHandler(handler string) error {
    p.twitterHandler = handler
    return nil
}

func (p *Person) TwitterHandler() string {
    return p.twitterHandler
}

Interfaces

The Go language does not require you to declare support for interfaces. Provided the type you pass has the functionality then it supports the interface. In the above object (hmmm class) the type provides an function called ServeHTTP(RepsonseWriter, *Request) and therefore implements the interface type Handler. (see https://golang.org/pkg/net/http/#Handler)

To define an interface you just need to define your interface.

package organization

type Identifiable interface {
   ID() string
}

Any object (hmmm class) which implements ID() string is an interface

type Person struct {
}

func (p Person) ID() string {
    return "12345"
}

Workflow

Looping

Introduction

All loops in GO are for loops. There are four types of for loops. GO supports break and continue.

Loop till condition

No surprises, prints 5 times

var i int
for i < 5 {
    i++
}

Loop till condition With Post clause

We have the traditional loop. It takes the form

var i int
for i = 0;i < 5; i ++ {
    fmt.printLn(i)
}

// Also valid
for ;i < 5; i ++ {
    fmt.printLn(i)
}

Infinite loop

Happy looping.

for {
    fmt.printLn(i)
}

// Also valid
for ; ; {
    fmt.printLn(i)
}

Loop over collections

This is like a foreach

slice := []int{1,2,3}
for i, v := range slice {
    fmt.printLn(i, v)
}

// Output index, value or 
/ 0 1
//1 2
//2 3

// Using a Map
wellKnownPorts := map[string]int{"http":80, "https":443, "ssh":22}
for k, v := range wellKnownPorts {
    fmt.printLn(k, v)
}

// Output key, value or 
// http 80
// https 443
// ssh 22

// Only Keys
wellKnownPorts := map[string]int{"http":80, "https":443, "ssh":22}
for k, _ := range wellKnownPorts {
    fmt.printLn(k)
}

// Only Values
wellKnownPorts := map[string]int{"http":80, "https":443, "ssh":22}
for _, v := range wellKnownPorts {
    fmt.printLn(v)
}

Branching

If Statements

Example is provided below

if u1.ID == u2.ID {
   // Blah
} else if u1.FirstName == u2.FirstName {
} else {
}

Switch Statements

With GO switch statements there is no fall through.

switch r.Method {
   case "GET":
       println("Get Request")
   case "DELETE":
       println("Delete Request")
   case "POST":
       println("Post Request")
   default:
       println("Default Request")
}

We can add a expression and switch on type

func foo(id interface{}) string
    switch v := id.(type) {
       case string:
           return v
       case int:
           return strconv(v)
...
}

Exceptions

Introduction

We don't have exceptions but we do have panics.The panic function stops the execution for the current function

func test() {
   fmt.println("Fred Was Ere")
   panic("was err")
   fmt.println("Fred has left the building")
}

Recovering

recover is a builtin function that is used to regain control of a panicking program.

The signature of recover function is provided below,

func recover() interface{}

Recover is useful only when called inside deferred functions. Executing a call to recover inside a deferred function stops the panicking sequence by restoring normal execution and retrieves the error message passed to the panic function call. If recover is called outside the deferred function, it will not stop a panicking sequence.

A example of recover() is provided below.

package main

import (  
    "fmt"
)

func recoverFullName() {  
    if r := recover(); r!= nil {
        fmt.Println("recovered from ", r)
    }
}

func fullName(firstName *string, lastName *string) {  
    defer recoverFullName()
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

Concurrency

Introduction

Thread Programming includes

  • Have own execution stack
  • Fixed stack space (around 1MB)
  • Managed by OS

Goroutines

  • Have own execution stack
  • Variable stack space (starts @2 KB)
  • Manage by Go runtime

So before showing go routines some definitions.

  • Concurrency is when two or more tasks can start, run, and complete in overlapping time periods. It doesn't necessarily mean they'll ever both be running at the same instant. For example, multitasking on a single-core machine.
  • Parallelism is when tasks literally run at the same time, e.g., on a multicore processor.

Goroutines

To create a goroutine you simply need to put the go keyword before a function. In the example provided we had a read from the database and cache in a for loop

	for i := 0; i < 10; i++ {
		id := rnd.Intn(10) + 1
		if book, ok := queryCache(id); ok {
			fmt.Println("Go from Cache")
			fmt.Println(book)
			continue
		}

		if book, ok := queryDatabase(id); ok {
			fmt.Println("Go from Database")
			fmt.Println(book)
			continue
		}

		fmt.Printf("Could not find book with id '%v'\n", id)
		time.Sleep(150 * time.Millisecond)
	}

By creating anonymous functions and adding the go keyword we now have go routines. Note the logic has changed as both DB and Cache functions are executed. We also do not cater for waiting for the functions to end before completion of the code.

	for i := 0; i < 10; i++ {
		id := rnd.Intn(10) + 1

		go func(id int) {
			if book, ok := queryCache(id); ok {
				fmt.Println("Go from Cache")
				fmt.Println(book)
			}
		}(id)

		go func(id int) {
			if book, ok := queryDatabase(id); ok {
				fmt.Println("Go from Database")
				fmt.Println(book)
			}
		}(id)

		fmt.Printf("Could not find book with id '%v'\n", id)
		time.Sleep(150 * time.Millisecond)
	}

Sync Package

You can find document on https://golang.org/pkg/sync

WaitGroup

A WaitGroup waits for a collection of goroutines to finish.
To use this you need to

  • Create a pointer to a WaitGroup
  • For each function in the group
    • Add WaitGroup to the goroutine
    • Not sure why, but call the WaitGroup.Add() function
    • Call WaitGroup.Done() upon completion
  • Call WaitGroup.Wait()
	wg := &sync.WaitGroup{}

	for i := 0; i < 10; i++ {
		id := rnd.Intn(10) + 1

		wg.Add(1)
		go func(id int, wg *sync.WaitGroup) {
			if book, ok := queryCache(id); ok {
				fmt.Println("Go from Cache")
				fmt.Println(book)
			}
			wg.Done()
		}(id, wg)

		wg.Add(1)
		go func(id int, wg *sync.WaitGroup) {
			if book, ok := queryDatabase(id); ok {
				fmt.Println("Go from Database")
				fmt.Println(book)
			}
			wg.Done()
		}(id, wg)

		fmt.Printf("Could not find book with id '%v'\n", id)
		time.Sleep(150 * time.Millisecond)
	}
	wg.Wait()

Mutex

A mutex is a mutual exclusion lock. In the above example the cache is being added to at the same time as being read from. We need to fix this and Mutex is what allows this.

go RACE

We can identify issues with our code by running

go run --race .

In the above code this may output

Could not find book with id '3'
==================
WARNING: DATA RACE
Read at 0x00c00000c288 by goroutine 15:
  main.main.func1()
      /home/iwiseman/dev/go/concurrent/main.go:46 +0xbe

Previous write at 0x00c00000c288 by goroutine 14:
  main.queryDatabase()
      /home/iwiseman/dev/go/concurrent/main.go:55 +0x1c9
  main.main.func2()
      /home/iwiseman/dev/go/concurrent/main.go:32 +0x6b

Goroutine 15 (running) created at:
  main.main()
      /home/iwiseman/dev/go/concurrent/main.go:22 +0x17d

Goroutine 14 (finished) created at:
  main.main()
      /home/iwiseman/dev/go/concurrent/main.go:31 +0x1c0

...
Found 2 data race(s)
exit status 66

This shows issues with the functions where line 15 was racing with line 14

Implementing a Mutex

We need to

  • Create the mutex
  • Pass it to the go functions
  • Pass it to the effected function and call lock and unfortunately unlock too
	wg := &sync.WaitGroup{}
	m := &sync.Mutex{}

	for i := 0; i < 10; i++ {
		id := rnd.Intn(10) + 1

		wg.Add(1)
		go func(id int, wg *sync.WaitGroup, m *sync.Mutex) {
			if book, ok := queryCache(id, m); ok {
				fmt.Println("Go from Cache")
				fmt.Println(book)
			}
			wg.Done()
		}(id, wg, m)

		wg.Add(1)
		go func(id int, wg *sync.WaitGroup, m *sync.Mutex) {
			if book, ok := queryDatabase(id,m); ok {
				fmt.Println("Go from Database")
				fmt.Println(book)
			}
			wg.Done()
		}(id, wg, m)

		fmt.Printf("Could not find book with id '%v'\n", id)
		time.Sleep(150 * time.Millisecond)
	}
	wg.Wait()

And here is the queryDatabase updated with the mutex. In a lot of modern languages unlock is not required but it is in GO

func queryDatabase(id int,m *sync.Mutex) (Book, bool) {

	time.Sleep(100 * time.Millisecond)
	for _, book := range books {
		if book.ID == id {
			m.Lock()
			cache[id] = book
			m.Unlock()
			return book, true
		}
	}
	return Book{}, false
}

RWMutex

The RWMutex provides a mutex where you can specify the type of lock. For reading the resource you can use RLock() which is more efficient. I have not yet understood why any mutex is required for reading but there you go.

Channels

Don't communicate by sharing memory, share memory by communicating - Rob Pike

Creating a Channel

To create channels you call the make function with the chan keyword, with the type of data and optionally the number of buffers.

func foo() {
	wg := &sync.WaitGroup{}
	ch := make(chan int)

	wg.Add(1)
	go func(ch chan int, wg *sync.WaitGroup) {
		myInt := <-ch
		fmt.Println(myInt)
		wg.Done()
	}(ch, wg)

	wg.Add(1)
	go func(ch chan int, wg *sync.WaitGroup) {
		ch <- 42
		wg.Done()
	}(ch, wg)
	wg.Wait()
}

Buffered Channels

By default channels are unbuffered, meaning that they will only accept sends (chan <-) if there is a corresponding receive (<- chan) ready to receive the sent value. Buffered channels accept a limited number of values without a corresponding receiver for those values.

Here we make a channel of strings buffering up to 2 values. Because this channel is buffered, we can send these values into the channel without a corresponding concurrent receive.

package main
import "fmt"
func main() {

    messages := make(chan string, 2)

    messages <- "buffered"
    messages <- "channel"

    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

Channel types

Although all channels are created bi-directional, when we pass channels to functions they can be one of three types of channels.

  • Bidirectional Channel
  • Send-Only
  • Read-Only
func foo(ch chan int) {...} // bi-directional
func foo(ch chan<- int) {...} // send-only
func foo(ch <-chan int) {...} // read-only

Closing Channels

Closing is done by calling close() which can only be done on the sending side. Sending to a closed channel will result in a panic. If you try to get a value on the receiving side and the channel is closed you will receive a 0.

Control Flow

if statements

Example

Using the comma, ok syntax allow us to understand if a value was received as ok returns false.

	wg := &sync.WaitGroup{}
	ch := make(chan int)

	wg.Add(1)
	go func(ch chan int, wg *sync.WaitGroup) {
		if msg, ok := <-ch {
		    fmt.Println(msg,ok)
                }
		wg.Done()
	}(ch, wg)

Multiple If/Else

This is known as the if-else-if ladder In GO we can have or not have the brackets.

        if (condition1)
        {  
            // code to be executed if condition1 is true  
        }
        else if condition2
        {  
            // code to be executed if condition2 is true  
        }  
        ... 
        else
        {
            // code to be executed if all the conditions are false  
        }

Initializer

for loops

This is the only loop statement available in Go.

Example

For loops can be used to read until the close of the channel. Without the close on the send side there would be a dead lock.

	wg := &sync.WaitGroup{}
	ch := make(chan int)

	wg.Add(1)
	go func(ch chan int, wg *sync.WaitGroup) {
		for msg := chan {
			println(msg)
		}
		wg.Done()
	}(ch, wg)

Iteration/Traversal Example

This example is for traversing a linked list.

// Here is our type
type linkedList struct {
}
  value int
  point *linkedList
}

for ; list != nil; list = list.pointer {
  fmt.Printf("%v\n", list.value)
}

Run Until Example

We can use range to indicate when to stop. We can use range with

  • Array or slice: The first value returned in case of array or slice is index and the second value is element.
  • String: The first value returned in string is index and the second value is rune int.
  • Map: The first value returned in map is key and the second value is the value of the key-value pair in map.
  • Channel: The first value returned in channel is element and the second value is none.
	fmt.Println("Hello, playground")
	
	aString := "FRED WAS ERE"
	for _, c := range aString {
   		log.Printf("%c\n", c);
	}

Select

Select is similar to select in c++. You can have

  • block until data available
  • disable a channel
  • never block

Blocking until data available

Blocks until there's data available on ch1 or ch2

select {
case <-ch1:
    fmt.Println("Received from ch1")
case <-ch2:
    fmt.Println("Received from ch2")
}

Disabling a channel

We can force a channel not to be used

ch1 = nil // disables this channel
select {
case <-ch1:
    fmt.Println("Received from ch1") // will not happen
case <-ch2:
    fmt.Println("Received from ch2")
}

Never Block

This select has default and therefore will never block.

select {
case x := <-ch:
    fmt.Println("Received", x)
default:
    fmt.Println("Nothing available")
}

Concurrency Worked Example

Here is the worked example. It delegates the reporting of the reading of the books to a new goroutine showing the usage of select. Note this does not work without the sleep to the queryDatabase() routine.

	wg := &sync.WaitGroup{}
	m := &sync.RWMutex{}
	cacheCh := make(chan Book)
	dbCh := make(chan Book)

	for i := 0; i < 10; i++ {
		id := rnd.Intn(10) + 1
		wg.Add(2)
		go func(id int, wg *sync.WaitGroup, m *sync.RWMutex, ch chan<- Book) {
			if b, ok := queryCache(id, m); ok {
				ch <- b
			}
			wg.Done()
		}(id, wg, m, cacheCh)
		go func(id int, wg *sync.WaitGroup, m *sync.RWMutex, ch chan<- Book) {
			if b, ok := queryDatabase(id); ok {
				m.Lock()
				cache[id] = b
				m.Unlock()
				ch <- b
			}
			wg.Done()
		}(id, wg, m, dbCh)

		go func(cacheCh, dbCh <-chan Book) {
			select {
			case b := <-cacheCh:
				fmt.Println("from cache")
				fmt.Println(b)
				<-dbCh
			case b := <-dbCh:
				fmt.Println("from database")
				fmt.Println(b)
			}
		}(cacheCh, dbCh)
		time.Sleep(150 * time.Millisecond)
	}
	wg.Wait()

Golang Tools

Generate struct from Json

go get -u github.com/twpayne/go-jsonstruct/cmd/gojsonstruct
gojsonstruct < my.json

Golang and VSCode

Environment

This is set in my .bashrc and is currently set to

export GOROOT=/usr/local/go
export GOPATH=/home/iwiseman/dev/projects/go
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH

Which using go env gives

GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/iwiseman/.cache/go-build"
GOENV="/home/iwiseman/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/iwiseman/dev/projects/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/iwiseman/dev/projects/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.17.1"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build2170854496=/tmp/go-build -gno-record-gcc-switches"

Plugins

I only use the standard plugin todate which is at v0.28.1
GO VSCode.png

Debbuging

You do not need any setting to launch the debugger. You do need to install delve which you can do with.

go install github.com/go-delve/delve/cmd/dlv@latest

Then just highlight the main.go and press F5.

Using Private Repositories

In order to use private repositories you need to do the following. where the TOKEN is an Access Token and the username is your username you use to login with.

git config --global url."https://gitlab-ci-token:<TOKEN>@gitlab.com/".insteadOf https://gitlab.com/
export GOPRIVATE=gitlab.com/<username>

Working with Local Libraries

If you need to work with a local library which is part of your solution you can do this by modifying the go.mod of the application to point to the dependencies of the local copy of the library. In my case the library was bookstore_oauth-go.

module gitlab.com/bibble235/bookstore_users-api

go 1.17

replace	gitlab.com/bibble235/bookstore_oauth-go => /home/iwiseman/dev/projects/go/src/gitlab.com/bibble235/bookstore_oauth-go

require github.com/gin-gonic/gin v1.7.4

require (
	github.com/gin-contrib/sse v0.1.0 // indirect
	github.com/go-playground/locales v0.13.0 // indirect
	github.com/go-playground/universal-translator v0.17.0 // indirect
	github.com/go-playground/validator/v10 v10.4.1 // indirect
	gitlab.com/bibble235/bookstore_oauth-go v0.0.0-20211010031644-734f6fed09f9
...
)

Package Management with dep (depreciated)

Go looks for packages in the following order assuming the defaults.

vendor
    src
      gitlab.com/bibble235/project
$GOPATH $HOME/go/src
$GOROOT /usr/local/go/src

The dep was a tool for managing this and can still be install with apt. To use dep go to the folder containing the main.go and execute

dep init

This will copy all of the dependencies into the vendor branch and create Gopkg.toml of each package.