background contour

Go | Golang

Resources

Udemy - Learn Go for Beginners Crash Course

Installation

https://go.dev/doc/install

Installation - VS Code

1. Extensions > Go

2. cmd + shift + p to open command palette in VS Code

3. install / update tools > select all checkboxes and install

Getting Started

Starting a project

Create a main.go file.

Files require a package header declaration.

package main
    
import "fmt"

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

Running a project

go run .

Building a Project

go build -o {{name}} main.go

Packaging

go mod init {{name}}
go mod tidy

Tooling

gofmt / go fmt (formatting)
go vet (linting)
delve (debugging)

Variables

Standard Declaration

var myVar string = "something"
altVar := "something"

Constant Declaration

const constVar = "something"

Naming Convention

var privateArg = `private` // camel-case are private

var PublicArg = `public` // pascal-case are public

Types

Basic Types

rune(char), string, int, float, bool

Aggregate Types

1. Array strings - []string

var animals []string
animals = append(animals, "dog")
animals = append(animals, "cat")
animals = append(animals, "bird")

animals[0] // "dog"

indexToRemove := 1
slices.Delete(animals, indexToRemove, indexToRemove + 1)
fmt.Println(animals) // ["dog", "bird"]

2. Array of Array of Strings - [][]string

3. Struct

type User struct {
    UserName string
    Age int
    FavouriteNumber float64
}

Reference Types

1. Hashmap - Map[string]string

// hashmaps do not maintain order
intMap := make(map[string]int)
intMap["one"] = 1

for key, val := range intMap {
    fmt.Println(key, val)
}

delete(intMap, "one")

val, ok := intMap["four"]
if ok { fmt.Println(val, "is in map") }

2. Pointer - &x | *pointer

x := 10
pointer := &x

*pointer = 15
fmt.Println(x) // 15

3. Channels

Mostly used by goroutines (concurrency)

Blocking

func listener() {
    key := chn
    fmt.Println(key) // 'a'
}

func main () {
    chn := make(chan rune)
    go listener()

    chn <- 'a' // trigger
}

4. Interface Types

type Animal interface {
says() string
howManyLegs() int
}

type Dog struct {
    Sound string
    NumberOfLegs()
}

func (d *Dog) says() { 
    return d.Sound
}

Decision

If-Else

x := true
if x {
    fmt.Println("hi x")
} else {
    fmt.Println("where did x go?")  
}

Switch

Fancy if-else

val := "some-case"
switch val {
    case "a":
      // do something
    case "b":
      // do something
    default:
      fmt.Println("case not found")
}

Select

Used mostly together with channels

Allows for concurrent channel operations

package main
        
func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "ch1 done"
    }()

    go func() {
        time.Sleep(3 * time.Second)
        ch2 <- "ch2 done"
      }()
      
    select {
        case msg1 := <- ch1:
          fmt.Println(msg1)
        case msg2 := <- ch2:
          fmt.Println(msg2)
        case <- time.After(5 * time.Second):
          fmt.Println("timeout")
    }
}

Coding Flow

Looping

Standard 3 part loop

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

Conditinal loop

func main() {
    i := 1000
    for i > 100 {
        fmt.Println(i)
        i++
    }
}

Infinite loop

for { ... }

Debugging

Delve

requires a go.mod file - do go mod init {{name}}

Create a launch.json file

Uses VS Code red dot breakpoint

Run > Start Debugging

//launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "main.go"
        }
    ]
}

Comma-Ok

Go's way of error handling.

Returns a tuple - (value, err / ok)

Check against err / ok to know if the function ran correctly

// operation
value, ok := someFunction()

// channel
value, ok := <- chn

// maps
value, ok := myMap[key]

String Interpolation

%s - string

%d - int

%.2f - float 2 decimal place

%t - bool

fmt.Printf("%s %d %t", "hi", 1, True)

Regex

reg, err := regexp.Compile("[^a-zA-Z0-9]+")
input = reg.RepalceAllString(input, "")

Operators

Math

add := 5 + 2
subtract := 5 - 2
divide := 1 / 2
multiply := 2 * 3
bracket := (2 + 3) * 5
power := math.Pow(2, 3)
remainder := 5 % 2
multiplyShort *= 2
divideShort /= 2

Unary

x := 3
x++
x--

Logical

greater x > y
lesser x < y
logicalAnd &&
logicalOr ||

Race Conditions

goRoutines can result in race conditions.

Resolve by using mutex

Test and check using --race flag

var msg string
var wg sync..WaitGroup

func updateMsg(s string) {
    defer wg.Done()
    msg = s
}

func main() {
    msg = "Hello world"

    wg.Add(2)
    go updateMessage("hello universe")
    go updateMessage("hello there")
    wg.Wait()

    fmt.Println(msg)
}

Codes with race conditions will show up with a warning

go run --race .
var msg string
var wg sync..WaitGroup

func updateMsg(s string, m *sync.Mutex) {
    defer wg.Done()
    defer m.Unlock()

    m.Lock()
    msg = s
}

func main() {
    msg = "Hello world"
    var mutex = sync.Mutex

    wg.Add(2)
    go updateMessage("hello universe", &mutex)
    go updateMessage("hello there", &mutex)
    wg.Wait()

    fmt.Println(msg)
}

Channel VS Mutex

Channel -

Passes data, distributes units of work, communicate async results

Mutex -

cache, state

Concurrency Patterns

Worker Pools

Use a limited pool of workers to work against a queue

func worker(
    id int,
    jobs <- chan int,
    results chan <- int,
    wg *sync.WaitGroup
){
    defer wg.Done()
    for i := range jobs {
        results <- j*2
    }
}
    

func main() {
    jobs := make(chan int, 5)
    results := make(chan int, 5)
    var wg sync.WaitGroup

    // start 3 workers
    for w := 0; w <3; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // send jobs
    for j := 1; j <= 20; j++ {
        jobs <- j
    }
    close(jobs)

    wg.Wait()
    close(results)

    for r := range results {
        fmt.Println("results ", r)
    }
}

Fan-in Fan-out

Expands a pool of workers, then merges the results

func generator(nums ...int) <- chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
          out <- n
          time.Sleep(100 * time.Millisecond)
        }
        close(out)
    }()
    return out
}
    
type result struct {
    workerId int
    input int
    output int
}
    
func worker(id int, in <- chan int) <- chan result {
    res := make(chan result)
    go func() {
      for n:= range in {
        res <- result{
          workerId: id,
          input: n,
          output: n*n
        }
      }
      close(res)
    }()
    return res
}
    
func merge(workers ...<-chan result) <- chan result {
    var wg sync.WaitGroup
    out := make(chan result)

    output := func(worker <- chan result) {
        for res := range worker {
          out <- res
        }
        wg.Done()
    }

    wg.Add(len(workers))
    for _, worker := range workers {
        go output(worker)
    }

    go func() {
        wg.Wait()
        close(out)
    }()

    return out
}
    
func main() {
    in := generator(1,2,3,...,15)
    var workers [] <- chan result

    for i := 0; i <5; i++ {
        workers = append(workers, worker(i, in))
    }

    for res := range merge(workers...) {
        fmt.Println(res)
    }
}

Pipeline

Chains calls in sequence

func add(nums ...int) <- chan int {
    out := make(chan int)

    go func(){
        for _, n := range nums {
          out <- n
        }
        close(out)
    }()
    return out
}
    
func sq(in <- chan int) <- chan int {
    out := make(chan int)

    go func(){
        for n := range in {
          out <- n*n
        }
        close(out)
    }()
    return out
}
    
func main() {
    for n := range sq(sq(add(1,2,3,4,5))) {
      fmt.Println(n)
    }
}

Testing

// xxx_test.go
        
func TestAdd(t *testing.T) {
    got := addTwoNumbers(2,3)
    want := 5

    if got != want {
        t.Error("got %d, wanted %d", got, want)
    }
}
// run current dir, verbose
go test . -v

// run all tests
go test ./...

// run only specific test
go test ./xxx -run TestFunctionName -v

HTTP Server

Raw HTTP server

func homePage(w. http.ResponseWrite, r *http.Request) {
    html := <strong>htllo world</strong>
    w.Header().Set("Content-Type", "text/html)
    w.Write(html)        
}
  
func main() {
    http.HandleFunc("/", homePage)
    http.ListenAndServe(":8000", nil)
}