go-digest

creators of golang

a small team of google

  • robert griesemer
  • rob pike
  • ken thompson (unix,unicode)

why create golang

google used three languages

  • python interpreted , has issues with large scale problems
  • java increasingly complex type system
  • CPP complex type system + compile time is very slow

why is cpp compile time slow ?

it is a legacy issue when c and cpp was designed computers did not have much memory so they had to be compiled at low memory so compile time was compromised

others vs golang concurrency

when python , java and cpp was designed computers had to do one task at a time so there was only one thread but now we need many concurrent things to happen so concurrency was added in later or patched at best . But at the the time of designing GO concurrency was there from the beginning .

Now comes go

  • strong and statically typed language (inharits from java and cpp ) strongly typed means the type of a variable can not change over time , statically typed means all variables have to be defined at compile time (there is a way to get around this which is called shadowing )

key features

  • Excellent community
  • simplicity
  • fast compile time
  • garbage collected language (trading systems have hard time with garbage collection)
  • built in concurrency
  • compiles to stand alone binary

Go Resources

a sample programm

package main 

import (

    "fmt"
)

func main() {

    fmt.println("hello there !")
}
// output -> hello there !

disection of the code

package main every programm has to be in a package and main is a special package as its the entry point

import ("fmt") generric import statement , fmt is a package for formatting string (usually pronunced as ফমট / fomt )

func main main function in the main package , ( entry point )

installation and project setup convension

variable

variable declaration

  1. var i int hear var is keyword for declaration , i is variable name and int is variable type
  2. var i int = 43
  3. short hand i:=43 , hear compiler determines what type it is and assigns value
  4. ​​​ package main 
    ​​​ var  i int = 27
    ​​​ func main(){
    ​​​     // code 
    ​​​ }
    

hear i is a package level variable

  1. we can also do this
var (
    i int = 0 
    j int = 1
)

var (
    k = 40 
)

a more organized way to decalre multiple variables

  1. SHOADOWNING
package main 

import "fmt"

vat i int = 10 

func main () {

    var i int = 20 
    
    fmr.println(i)
}
// output -> 20 

ther same variable in the inner scope gets preference this is called SHADOWING , this allows bothe the variable value and type to be changed

  1. decalred valirables in GO have to be used or will cause compiler error
  2. NOMIANL CONVENSION there are 3 levels of visibility in GO
    • package level if first letter of variable name is of lower case then it is scoped to the package and file in the same package can access the variable
    • if it is upper case at the package level then it is exported from the package and it is globally visible
    • block scoped ( inside {} ) , the variable is never visible outside the block
    • there are no private scope

type conversion

var i float  = 42.5 

var j int 

j = int(i)

Primitive types

  1. Boolean
  2. Number (int , float , complex)
  3. text (string , rune)
  • Boolean
var r bool = true 
n := 1 == 1 // true 
m := 1 == 2 // false 

unassinged bool variable has default value as false

type conversion is not posssible for bool , so no bool -> int or int -> bool possible

  • Numeric for go in every system int is treated differently , but it is farunteed that it would at least be 32 bit but based on the system it could also be 64 bits or 128 bits
  1. default integer keyword is int

signed

int8 -128 to 127
int16 -32786 to 32767
int32 - 2 billion to 2 billion
int64 -9 quintillion to 9 quintillion

if bigger number is needed theres a way in math package to get arbitary big numbers

unsigned

var n uint8 = 43

uint8 0 to 255
uint16 0 to 66535
uint32 0 to 4 billion

there are no 64 bit unsigned integer . byte type which is alias of unit8 as it is very "famouse"

basic arethmatic

sign operation
+ add
- subtract
* multipply
/ division
% reminder

cat operate on different type on GO , there are no type coertion like JS , explicit type conversion has to be done in GO

bit operations

sign operation
& and
| or
^ xor

bit shifting

a := 8 
a << 3  // 64 
a >> 3  // 1 

Decimal (32 & 64 bit)

float32 ± 1.18E-38 to ± 3.4E38
float64 ± 2.23E-308 to += 1.8E308
​n := 3.14 // this will be by default float 64 
​n := 13.72
​n := 1E14 

bit operator dont work with float

complex type

NB. no other language treats complex type as a basic type that why GO is suitable for data science

there are two complex type based on capacity

// 1. complex64
// 2. complex128
var n complex64 = 1 + 2i 
real(n) // output -> float 
imag(n) // output -> float 
// complex64  = raal(float64)  + imag(float64)
// complex128 = raal(float128) + imag(float128)
complex(5,12) // output 5 + 2i

Text Type (string and rune)

string

String is array of utf-8 ( 8 bit )charecters , so it can encode every charecter avaiable thatsw why we need other type (rune) ,

string can be treated as array

byte is an alias of string

s := "this is a string"
fmt.printf("%v , %T",s[1],s[1]) 
// output -> h , uint-8 

ther is psudo arethmetic support for string s1 + s2

 b := []byte(s) 
 // type casted to byte array , this is also a collection of 
 // utf-8/uint-8
 // this is also know as byte slicing 

byte slicing is needed when sending response to ohter services over the web

Rune

in utf-32 any charecter can be 32 bits long but does not have to be 32 bit long . so a valid utf-8 is a valid utf-32 charecter (this is a bit tricky)

r := 'a' 

or

var r rune = 'a' 
fmt.println("%s , %T",r,r)
// output -> r , int32

rune is type alisa of int32 , ther is a built in function named ReadRune

constants

const myConst  int = 42 
import "math"

const myConst float64 = math.Sin(1.57) // ❌

the above code will cause error beacuse const has to be assigned at compile time and the function will execute at runtime

NB. arrays/collections are mutable types by default and they can not be typed constant

const can be shadowed

// package level 
const a  int64 = 24 

// block level 
const a int = 43 

const is untyped , the compiler simply replaces all a with 10 , so the below code will cause error

const a int32 = 10 
var b int8 = 20

a + b // ❌

enumarated constants

iota is used for counter in a scope

ex-1

// package scode 

const a  = iota 

fmt.println(a) // output 0 

ex-2

const (
    a = iota 
    b = iota 
    c = iota
)

// print(a,b,c) -> 0 , 1 , 2

ex-3

const (
    a = iota 
    b 
    c
)
// output -> 0 , 1, 2 
const (
    a2 =iota 
)
// output -> 0 

ex-4

// package level 

const (

    catSpecialist = iota 
    dogSpecialist 
    snkaeSpecialist
)

func main() {

    var specialist int = catSpecialist 
    
    // print(specialist == catSpecialist) -> true 
}

ex-5

// package level 

const (

    catSpecialist = iota 
    dogSpecialist 
    snkaeSpecialist
)

func main() {

    var specialist int  
    
    // print(specialist == catSpecialist) -> true 
}

default int value is 0 amd iota starting value is also 0 , this might cause unexpected error

to solve this issue we can initiate iota with error value

const (
    errorSpecialist = iota 
    catSpecialist 
    dogSpecialist 
)

ex-6

const (
     _ = iota // tells the compiler i dont care about the value 
     cat 
     dog 
)
// print(cat,dog) ->  1 , 2

we can also offset iota like this

 const (
     cat = iota + 5 
     dog 
     snake 
 )
 // print(cat,dog,snake) -> 5 , 6 , 7
 

ex-7 (example of file size const with iota)

const (
    _ = iota 
    KB = 1  << (10 * iota)
    MB
    GB
    TB
    OB
    EB
    ZB
    YB
)

func main () {

    filesize := 40000000
    fmt.println("%.2f GB", filesize / GB)
    // output 3.73 GB

}

ex-8 (role and permissions implementation with iota)

const  (
    isAdmin = 1 << iota 
    isHeadquater
    canSeeFinantial
    canSeeAfrica
    canSeeAsia
    canSeeEurope
    canSeeNorthAmerica
    canSeeSouthAfrica
)

func main () {
    
    var roles byte = isAdmin | canSeeFinancial | canSeeEurope

    fmt.println("%b",rules)
    
    // output -> 1 0 0 1 0 1
    
    fmt.println("is admin %v ", (isAdmin & rules) == true)
    
    // output -> true 
    
    fmt.println("is headwauater %v ", (isHeadwauater & rules) == true)
    
    // output -> false 
}

isAdmin 1 << iota 0 0 0 0 0 0 0 1
isHeadquater 0 0 0 0 0 0 1 0
canSeeFinantial 0 0 0 0 0 1 0 0
canSeeAfrica 0 0 0 0 1 0 0 0
canSeeAsia 0 0 0 1 0 0 0 0
canSeeEurope 0 0 1 0 0 0 0 0
canSeeNorthAmerica 0 1 0 0 0 0 0 0
canSeeSouthAfrica 1 0 0 0 0 0 0 0

Array and Slices

Array

method-1

 grades := [3]int {97,85,93}

method-2

 grades := [...]int {97,85,93} // unspecified array size 

__NB. empty array value is []

len() function is used to determine array length

2d array

var matrix = [3][3] int {

    [3]int{1,1,1} ,
    [3]int{2,2,2} ,
    [3]int{3,3,3}
}

// print(matrix[0]) -> [3]int {1,1,1}

array in go are different than other languages , array in go are considered value

 a := [...]int{1,2,3}
 
 b := a // changing b wont effect a 

in other languages when copying an array we will be using the same data ( passing by reference) but in go when copying another exact copy of the array is made .

so why is this even important to note ?

when passing an array to a function , if the array is really big with millions of data the programm is going to be slowed down as copy by value is going to be occure

to avoid this use

 b := &a // pointer 

& is called address of operator

b := &a tells compiler b is going to point to the same data as a ( a and b will be pointing to the same data )

Slice

the length of an array has to be known at compile time which limits its usefullness , hear slice comes to rescue

a := []int{1,2,3} // this is a slice 

len() function is avaiable , cap() shows the capacity of slice

now for slice

b :=a 

a and b are pointing to the same data

    a := []int{1,2,3,4,5,6,7,8,9,10}
    b := a[:]
    c := a[3:]
    d := a[:6]
    e := a[3:6]

hear a , b , c ,d , e are pointing at the same data in different ranges ,

[3:6] hear is [inclusive:exclusive] , will output [4,5,6]

other ways to create slice

a := make([]int,3) 
// output [0,0,0], len-> 3 , cap -> 3

b := make([]int,3,100)
// output [0,0,0] , len -> 3 , cap -> 100

appending a slice

a = append(a,1)

ex-1

    a := []int{}
    // len(a) -> 0 
    // cap(a) -> 0
    // a -> []

ex-2

    a := []int{}
    a = append(a,1)
    // a -> [1]
    // len(a) -> 1
    // cap(a) -> 2 ❓ but why

explanation , we first created slice with 0 elements so go created an empty array , but when we appended 1 it could not fit so go had to create an new array (double is size )and copy all the values there ,

this is ok for a samll slice but as things get big copy operation gets costly ,

to avoid this we can use a suitable value is make functions third parameter , then append function would not need to copy to a new array

append

append can take two or more arguments

    a = append(a,1) // -> [1]
    a = append(a,2,3,4,5) // -> [1,2,3,4,5]

NB. if slice append exceeds the base size of the slice go is going to pow 2 the size of the underlying array 2 -> 4 -> 16

ex-2 (concatinate two slices in go)

a := []int{1,2,3}
a = append(a,[3]int{4,5,6}) // ❌

above code will not work as we are trying to append two differenct types array and slice

to make this work we have to spread the array

a = append(a,[3]int{4,5,6}...)

this becomes

a = append(a,4,5,6)

ex-3 (implement stack in go)

stack has push and pop functions . push will be done by append and pop can be done by shift operation

// pop first 
a := []int{1,2,3,4,5}
b := a[1:]
// pop last 
b := a[:len(a)-1] 

ex-4 (remove element from the middel of a slice)

lets assume we want to remove the third element from a slice

 a := []int{1,2,3,4,5,6,7}
 
 b := append(a[:2],a[:3]...)

we have to be careful as we are working with references as we will ses in the code below

 a := []int{1,2,3,4,5}
 fmt.Println(a) //  1 2 3 4 5 
 b := append(a[:2],a[:3]...) 
 fmt.Println(b) // 1 2 4 5 
 fmt.Println(a) // 1 2 4 5 5 , 🤷‍♀️ unexpected behavior 

we have to be careful of not using the same reference in other places

Other collection types

Maps

example

    statePopulation ;= map[string]int{
        "dhaka" : 2323423 ,
        "pabna" : 2131237
    }
    fmt.Println(statePopulation)
    // map[dhaka:2323423 pabna:2131237]

value for a map can be of any type but to be a key for a map the type has to be testable with euqality

for example string , array , numbers can be tested if they are equal 1 == 1 "arif" == "sarif" , but not slice and map

another way of creating map is make function

 mp := make(map[string]int)
 mp = map[string]int{
     "arif" : 27
 }

NB . the return order of map keys is not gaurenteed

remove entry from a map

    delete(mp,"arif")

default map key value is 0 . not existing key returns 0

so if a map has value as 0 how can we know if the key exists and is 0 or the does not exist thuse the value 0

    pop , ok = mp["jabed"] 
    // ok -> false 
    // pop -> 0 

if key existed ok would be true

how do we know how many elements are there in map

len(mp) // 1 

NB. mpas are passed by reference

Struct

another collection type

    type Doctor struct {
        number int 
        actorName string 
        companions []string 
    }
    
    func main () {
    
        doc := Doctor {
            number : 3
            actorName : "istiak"
            companions : []string {
                "sarif",
                "samad",
                "jabed"
            }
        }
    }

we could also write this as

doc := {
    3,
    "istiak",
    []string {
       "sarif",
       "samad",
       "jabed"
    }
}

for this approch order has to be maintained

doc.number // 3
doc.actorName // "istiak"
doc.companions[2] // "jabed"

thing about struct naming convension

struct name starting with capital letter will be exported but if the property names are not capitized they wont be visible outside the package

annonymu struct

 aDoctor := struct {name string} {
     name : "arif"
 }
 aDoctor.name // "arif"

used mostly in short lived use cases

NB. structs are passed by value not reference

to pass reference we can use address of operator (&)

    bDoctor = &aDoctor 

extra bits

go does not have traditional OOP for example it does not have inharitance but to implement inharitance we can use embaded struct

type Animal struct {
    Name  string 
    Origin string 
}

type Bird struct {
    Speed float32
    CanFly bool 
}

Go does not support isA relation but supports hasA relation or embaded

type Bird {
    Animal // embaded 
    Speed float32
    CanFly bool
}

we can say a bird has animal like characteristics

    b := Bird()
    b.name = "penguin"
    b.Origin = "alaska"
    b.Speed = 23.2
    b.CanFly = false 

but if we use literal syntax

    b := {
        Animal { Name : "penguin" , Origin : "alaska" }
        Speed : 23.2
        CanFly : false 
    }

Tags

tags just pass a string with property as metadata nothing else

    type Animal {
        Name string `required max:100`
        Origin string 
    }

tags are written in the right side of the property with backticks

to retrive tags

    import "reflect"
    
    t := reflect.TypeOf(Animal{})
    filed , _ = t.FieldByName("Name")
    field.Tag // -> 'required max:100'

So now if we list all the collection types in go they are

  1. Array
  2. Slice
  3. map
  4. Struct

Control Flow

basic example

    if true {
        fmt.Println("this is true ")
    }

in got we will alwasy have to use brances

    if initializer ; condition {
        // stuff 
    }
    population := map[string]int{
        "mirpur" : 10000 ,
        "baridhara" : 232000,
    }
    
    if data , ok = population["mirpur"] ; ok {
        fmt.Println(date) // initializer statement variables are scoped to this block 
    }

logical operator are same as other languages

NB. floating point evaluation has to be do

switch statement

func main() {
    
    var tag int = 1 
    switch tag {
        case 1 :
            // do stff 
        case 2 :
            // do stuff 
        default : 
            // do stuff 
    }
}

falling through ,

in other languages we do

    case 1 :
    case 2 :
    case 3 :
        // do stuff 

but in go there is no falling through instead we have the ability to test multiple test in a single case

    case 1 , 5 :
        // do stuff 
    case 2 , 4, 6 :
        // do stuff 

if some number is repeted in more then one case it is going to cause an error so the numbers have to be unique

NB. in to break statemetns are implicit , as break tends to cause errors when we forget to write it so go designers made it implicit

fallthrough in go if we put fallthrough keyword after any case the next code is going to execute

    i := 2
    switch i {
        case i < 10 :
            // code 
            fallthrough
        case i == 1 : 
            // code 
    }

hear event if i is 2 the code is i == 1 is going to execute beacuse of fallthrough

typed switch

    var i interface {} = 1 
    switch i.(type) {
        case int :    
            // i is an int 
        case float64 :
            // i is float64 
        case string : 
            // i is a string 
        default : 
            // i is another type 
    }

NB. i.(type) returns the type of a variable , but only inside switch case controll flow not outside switch

how do we catch [3]ubt{1,2,3} with typed switch in golang

    switch i {
        case [3]int : 
            // code 
    }

we can break statement explicitly to break out of a switch statement if we want

Looping

a simple loop

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

writing like below will cause error

    for i := 0 , j := 0 ; i < 5 ; i+=1 , j +=1 { // ❌
        // code 
    }

instead we have to write it like

    for i , j := 0 , 0 ; i < 5 ; i , j = i+1 , j+1 {
        // code 
    }

in the above code increment section if we write i++ and j++ , it would cause error as i++ is not an expression in go it is an statement

NB. expression is something that resolves to a value (returns a value )

while loop substitution with for in go , with only the condition section

    for i < 5 {
        // code 
    }

this is also valid

    for {
        // infinity loop 
    }

NB. break and continue are same as other languages (like c )

label in loop

there is a concept in golang called label , this can be used to break out of loops

    loop :
        for {
            for {
                break loop 
            }
        }

this might seem like c langs goto statement but its not . loop hear indicates from where do we want to break out of.

looping through other types of collections

    s := []int{10,20,30}
    
    for k,v := range s {
        fmt.Println(k,v)
    }
    
    // 0 1
    // 1 2 
    // 2 3
    

we can use this format with

  1. slice
  2. array
  3. map
  4. string
  5. channel (discussed latter in concurrent programming section)
    for k,v := range x {
        // code 
    }

only loop through keys

    for k := range x {
        // code 
    }

or

    for k , _ := range x {
        // code 
    }

only values

    for _ , v := range x {
        // code 
    }

_ is a special variable in go which is called the readonly variable and go compiler ignores it

Defer , Panic , Recover

DEFER function invokes a function but delays it execution to some future point in time

PANIC an application can panic , go application can enter a state when it can no longer continue to run and now go runtime trigger that or trigger it on our own

RECOVER when application starts to panic we need to somehow save the application and signal that to the rest of the application

    func main () {
        
        fmt.Println("start")
        
        defer fmt.Println("mid")
        
        fmt.Println("end")
        
    }
    // output
    // start 
    // end 
    // mid 

the way defer works hear it it delays the execution of the statement it moves it after the main function but before the main function returns

    func main () {
        
        defer fmt.Println("start")
        
        defer fmt.Println("mid")
        
        defer fmt.Println("end")
    }
    // output
    // end 
    // mid
    // start 
    

defer works LIFO (last in first out)

so the last function that gets deffered will be the first to get called

use cases we often use defer keyword to close out resource and it it logical that we close resources out in the opposit order we open them , as resource might be dependent on the other one

NB. defere statement executes after the main function is done but before the main function returns

practical use of differ function

this example is a sample rpogramm that requests a file from netwok and reads the contents of the file

import (
    'fmt',
    'io/util'
    'log'
    'net/http'
)

func main() {
    res , err = http.Get('https://google/robots.txt')
    if err != nil {
        log.Fatal(err)
    }
    
    robots , err = ioutil.ReadAll(res.Body) 
    
    res.Body.Close()
    
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Println("%s",robots),
}

in the above application defer can help with the body close . As we can seee we closed the file lines later to the request but htere is a prossibility that we might forget to close it later . Hear defer can help us after the request we can close e witha deffered keyword

so code could be like below

import (
    'fmt',
    'io/util'
    'log'
    'net/http'
)

func main() {
    res , err = http.Get('https://google/robots.txt')
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()
    robots , err = ioutil.ReadAll(res.Body) 
    
    
    
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Println("%s",robots),
}

NB. the most common use case for using defer allows us to associate opening and closing of reses next to each other

A common pattern

  • Open resource
  • check for error
  • close the resource with differ
  • we have to check for error beacuse if the resource never opens trying to close it will cause error

NB. when we are opening a lot of resources say with a loop then using defer might not be the best choice as defer will execute before main returns so milions of resources will stay oen and close all togather at the end . we might explicitly close then after are done delegate resource handelling to another function and let it handel the closing of it

    func main () {
        
         a := "start"
         defer fmt.Println(a)
         a = "end"
    }
    // output 
    // "start"

defer takes the value of a variable at the time defer was called

Panic

In go there are no Exception . like most languages what is said to be exceptional like most it go it is quite normal .

lets sat we are trying to open a file that does not exist many languages will consider it as an exception but go will simply return an error value

however there are cases where a Go application can not continue which in go is called panic

Ex

    func main () {
        a,b := 1,0
        and := a/b
        fmt.Println(ans)
    }
    // output 
    // panic : runtime error : integer devided by zero 
    // {stack trace}

ex

    fmt.Println("start")
    panic(" something bad happned")
    fmt.Println("end")
    
    // out
    // panic : something bad happned 
    // {stack trace }

NB.it is very rare that go is going to panic . it would in most cases return a error value its up to use how we handel it

NB.Panics happen after the deffered function are executed

ex

fmt.Println("start")
defer fmt.Println(" this was deffered ")
panic("something bad")
fmt.Println(" end ")
// output 
// start 
// this was deffered 
// panic : something bad 
// {stack trace}

recover function returns nil if application is not panicing else will return error that causes the application to panic

fmt.Println("start")
defer func () {

    if err :=recover() ; err != nil {
        log.Println(err)
    }
}
panic("something bad") 
fmt.Println("end") 


// output 
// start 
// log-time : something baf 

hear we see recover catches the error but our programm still exists . But recover still has impact if we have depper call stack which we will see in the next example

ex

func () { 
  fmt.Println( " start " ) 
  panicker()
  fmt.Println("end") // <- will execute 
} 


func panicker () {
   fmt.Println(" about to panic " ) 
   defer func () {
     if err := recover() ; err != nil {
       log.Println(err)
     }
   }
   
   panic(" something bad")
   fmt.Println(" donr panicing " ) // will not execute 
}


// output 
// start 
// about to panic 
// error 
// end

when the panic occurs in the panicker function . the usual execution will stop and execute the derrered function the deffered function recovers the application and the normal excution flow starts again

calling recover means that we are going to deal with what ever is wrong with but in case we cant we can re panic the application

so if we change the code a bit

if err := recover() ; err != nil {
   log.Println(err)
   panic(err) 
}

// start 
// about to panic 
// error : something  bad 
// panic : something bad 
// { stack trace } 

Pointer

agenda

  • creating pointer
  • dereferencing pointer
  • the new function
  • working with nil
  • types with internal pointer

ex

 var a int = 42 
 var b *int  = &a 
 fmt.Println(a,b)
 // 42 , 0x0040 
 fmt.Println(a,*b)
 // 42 , 42 
 a = 27 
 fmt.Println( a, b ) 
 // 27 , 27 
 *b = 10
 fmt.println(a,b)
 // 10 10

ex(pointer arithmetic)

a := [3]int{1,2,3}
b := &a[0] // ❌
c := &a[1] // ❌

go does not support pointer arithmetic built in. if we must use it we have to use the unsafe package

P.A was left out of go for simplicity as P.A can get very complicated

ex (how can we create pointer types )

type mySteuct struct {
  foo int 
}
func main () {

  var ms *myStruct // ms := &myStruct{foo:42}
  fmt.Println(ms) 
  
}

// output 
// &{42} 

another way to initialize variable to be pointed to an object is

var ms *myStruct 
ms = new (myStruct)
fmt.Println(ms)
// output 
// &{0} 

NB.with new we can only create empty objects

var ms *myStruct 
fmt.Println(ms)
// outout 
// <nil>

dereferencing with pointer

var ms *myStruct
ms = new(myStruct)
(*ms).foo = 42 
fmt.Println((*ms).foo)
// output 
// 42

the perenthesis are hear because dereferencing operation ( * ) has lower precidence then the dot (.) operator

without the perenthesis we would be dereferencing (ms.foo ) the whole thing

the above code can also be written as

var ms *myStruct
ms = new (myStruct)
ms.foo = 42 
fmt.Println(ms.foo)
// output 
// 42 

as go had limits of pointer it can help us out by making syntax easy , this is just syntactic sugar the compiler is helping us out (*ms).foo and ms.foo are same to the compiler hear

ex(why are slice / map copied by reference when assigned to another variable)

beacuse they are a pointer reference to an underlying array

    a := []int{1,2,3}
    b := a // <- passing pointer reference 
    fmt.Printn(a,b)
    a[1] = 42 

same thing with maps

ex(how to declare pointer of a type)

prefix the type with an asterics of the variable

var a *int 

Functions

agenda

  • basic syntax
  • parameter
  • return values
  • anoynymus function
  • function as type (first class citizen)
  • method (special king of function)

ex

function starts with func if function name is upper case it will be exported

func main () {
    // stuff 
}

parameters

func sayMessage(msg string) {
    fmt.Println(msg)
}

func main() {
    sayMessage("hello there")
}

if multiple parameters are of the same type we can specity the parameter at once

func test (geet,name string) {

}

we can pass parameters as pointer in a function for small values it might not matter much but for large data structures passing by pointer is efficient as the value need to be copied every single time

But passing maps and slices to a function is always as a pointer

variable parameters

func main () {
    sum(1,2,3,4,5,6)
    // output 
    // [1 2 3 4 5]
    // the sum is 15 
}

func sum(values ...int) { // tells the compiler to wrap all the parameters in a slice name values 
    fmt.Println(values)
    resut := 0 
    for _ , v  := range values {
        result += v
    }
    fmt.Println("the sum is ",result)
}

when passing variable parameter we can only pass one and it has to be the last one

return of function

func foo (values ...int) int { // hear int at end is the return type 
    // stuff 
    sum := 0 
    
    return sum 
}

NB. a rare feature that go has that other languages do not is go can return a loal variable as a pointer


func foo(values ...int) *int {
    sum := 0
    return &sum 
}

it is rare beacuse when we declare the sum variable we decalre it in the execution stack of this function which is a special section of the memory which is set aside for all the operation this function is going to be working with . when this function exits that execution stack is destroyed and memory is freed up

but he pointer would be pointing to a blank location with garbage value but in go lang when it realizes we are returning a value that is in the local stack it is autometically going to promote the variable in the shared memory on the compiler which is also called heap memory

named return value

    func sum (values ...int) (sum int) {
        for _ ,v := range values {
            sum += v
        } 
        return // will have to write return without specifying value 
    }

NB. this is helpful when the function is very big/long , to know the return type we have to scroll down at the end of the function , by using named return we can avoid that

multiple return values

    func main () {
        d , err = division(5.0,0)
        if(err != nil) {
            fmt.Println(err)
            return 
        }
        fmt.Println(d)
    }
    
    function division(a,b float64) (float64 , err) {
        if( b == 0) {
            return 0.0 , fmt.Error('cant divide by Zero')
        }
        return a/b , nil 
    }

NB. this pattern is very common in go this left justifies our code as much as possible as we dont have to write the arternative code isdise else block

Annoynimus function

func main () {
    func() { // this function can read outer scope variables 
        fmt.Println("hello there !")
    }() 
}
// output 
// hello there !

NB.if we want to read outer scope stuff we have to pass that as argument / parameter or it might cause error in async code

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

function as type

    var f  func() = func () {
        fmt.Println("hello GO")
    }
    f()

NB. as they are types they can be stored in variable

methods

func main () {
    g := greeter {
        greeting : 'hello',
        name : 'go'
    }
    g.greet() // method invocation 
    
 
}

type greeter struct {
    greeting string 
    name string 
}

func (g greeter) greet() {
    fmt.Println(g.greeting,g.name)
}
// (g greeter ) is known as method reciver 

NB. method reciver is what makes a function method in go , what this does is gives function a context were to execute , so we can do g.greet()

methods are functions with syntactic suger that provides a contet the function should execute

    func man struct {
        name string 
    }
    
    func (m man) getName()(name string) {
        return m.name 
    }
    
    func (m *man) setName(name string) {
        m.name = name 
        return m 
    }

getName uses a copy of man , but setName uses a reference to man which is more efficient

if we return pointer from a set method we can make a builder

func (m *man) setName(name string) *man {
    m.name = name 
    return m 
}
m := man {
    name : ''
}
m.setName("arif").setName("kasif")

Interfaces

" the way interfaces are implemented in GO makes it so scaleable compared to java and other languages "

agenda

  • basic
  • composing interfaces
  • type conversion
    • empty interface
    • type switching
  • implementing with values vs pointer
  • best practices
type Writer interface {
    write([]byte))(int , err)
}

type ConsoleWriter struct {} 

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

func main () {
    var w Writer = ConsoleWriter{} 
    w.Write([]byte("hello go"))
}

inside structs we define data but inside interfaces we describe behaviours (method defination)

in got we do no explicitly implement interface we do it implicitly

console writer hear can be replaced with any kind of struct that has method writer what is polymorphis behaviour

so what we can do is for a concreate type we can create an interface

naming convension of interface

if we have a single method interface we name the interface like method name + er

write+ er = writer

structs are the most common way to implement interfaces , but we dont have to use structs we can use any type

ex we dont have controll over int type as it is a primitive type we have controll over types that we define

    type Incrementer interface {
        Increment() int 
    }
    
    type IntCounter int 
    
    func (inc *IntCounter) Increment() int {
        *inc += 1 
        return int(*inc)
    }
    
    func main() {
        myInt := IntCounter(0) 
        var inc Incrementer = &myInt 
        for i :=0 ; i< 10 ; i +=1 {
            fmt.Println(inc.Increment())
        }
    }
    // output 
    // 1 
    // 2
    // 3 
    // .. 
    // .. 
    // 9 

interface implementing interface


type WriterCloser interface {
    Writer 
    Closer 
}

type Writer interface {
    Write([]byte) (int,err)
}
type Closer interface {
    Close() err
}
type BufferWriterCloser struct {
    buffer *bytes.Buffer 
}

func (bwc *BufferWriterCloser) Write (data []byte) (int , err){
    n , err := bwc.Buffer.Writer(data)
    if err != nil {
         return 0 , err    
    }
    
    v := make([]byte,8)
    
    for bwc.buffer.len() > 8 {
        _,err  := bwc.buffer.Read(v)
        if (err != nil) {
            return 0 , err 
        }
        _ , err2 = fmt.Println(string(v))
        
        if err2 != nil {
            return 0 , err2
        }
    }
}

fucn main () {
    
    var wc WriterCloser = new BufferWriterCloser() 
    wc.Write([]byte("hello youtube listeners, this is test"))
    we.Close()
}

func NewBUfferWriterCloser() *BufferWriterCloser {
    return &BufferWriterCloser {
        buffer : bytes.NewBuffer([]byte{})
    }
}

func (bwc *BufferWriterCloser) Close() error { // flushing the buffer 
    for bwc.buffer.len() > 0 {
        data := bwc.buffer.Next(8)
        _,err = fmt.Println(string(data))
        if err != nil {
            return err 
        }
    }
    return nil 
}

// output 
// hello yo
// utube li
// sterners ,
// this is 
// a test 
// -> charecters are getting printed with 8 charecters chunk 

if we comment out wc.Close() we would get output

// output 
// hello yo
// utube li
// sterners ,
// this is 
// -> there will be no `a test` printed out as this is not a 8 charecter chunk , the buffer is not flushed 

Type conversion

bwc := wc.(*BufferWriterCloser) 

converting writer closer to buffer writercloser but notice we are using pointer

bwc := wc(io.Reader) // -> this will cause error as io.Reader need read method so it panics 

to stop this panic hear we can

n , ok = := wc.(io.Reader)

if ok {
    
}else {
    // conversion failed 
}

r , ok = wc.(*BufferWriterCloser) // * <- is required as we implemented the interface with pointer 

// TODO this section needs more work

empty interface

var myObj interface {} = NewBufferWriterCloser() 

if wc , ok := myObj.(WriterCloser) ; ok {
    // stuff 
}

NB. nice thing about empty interface are everthing can be cast into an object that has no method written to it even primitivs

This is a very important note when working with interfac if any of the methods require a pointer or implementing own reciver we are going to have to implement that interface with a pointer if not through if all the methods require value type then we can go ahed and use value types but we could aslo use a pointer

implementing an interfce with value

var wc WriterCloser = myWriterCloser{}

implementing with pointer

var wc WriterCloser = &myWriterCloser

when we are implementing an interface with value type all of the methods need to have value recivers (methods that implement the interface) if we implement with pointers we just need the methods regardless of the the recivers

so the method set for a value type is all the method with value recivers but method set for a pointer type is all of the methods with value recivers as well as pointer recivers (only for interfaces )

what is method set ?

when we define types and we assign methods to them each one of those type has what is called a method set

now when we are working with methods directly the method set is all of the methods regardless of the recivers associated with that type

But with interfaces things work differently when we implement interface with concrete value the method set of the value , when we are taking in the context of interface is any method that has a value as a reciver and the method set if a pointer is the sum of all of the value reciver methods and all of the pointer reciver methods

need examples

Encouraged and supported by the go community and good to apply if our application its practical

  1. use many , small interfaces , the samller the interface the useful and powerful they can be (applicable for any language)

  2. if we dont need to xport and interface athen we wont (export concrete video ref (5.25.00)

  3. design functions and methods to recive interace whenever possible

Goroutines

Agenda

  • creating goroutines
  • syncronization
    • waitgroup
    • mutex
  • parallelism
  • best pranctices

ex how to create goroutine ?


func main() {

    go sayHello()

}

func sayHello() {

    print("hello")

}

// output 
// nothing would be output 

go sayHello() spins off a green thread and run the sayhello in the green thread .

the sayHello is in another go routine and our application exits as soon as the main function is done

most of the programming languages (PL) use os threads what it means they have an individual call stack handed to that thread these tend to be very very large talking 1mb of RAM quite bit of time for app to set up creation and distruction of thread is very expensive

GO follows a different model which is similar to Erlang language which is green thread which creates an abstruction of a thread which we call go routine . GO scheduler maps these go routines on to the os thread and schedular will take then with cpu thread that available and assign every go routine certain amount of time on these threads

Go routines are very cheap to create and destroy so a go application can have many go routines at once

ex

this is bad practice but just for example

go sayHello() 

time.Sleep( 100 * time.Miliseconds )

// out 
// hello 

ex (clouser)

go has understanding of clouser

    go func() {
    
    }()
    
    time.Sleep()
    
    // out 
    // hello 

     msg := "hello"
     
     go func () {
     
         print(msg)
     }()
     
     msg = "bye"
     
     time.Sleep()
     
     // output
     // bye 
     

as we see using clouser like this can have unexpected results so we can write is like


    msg := "hello"
    
    go func(){
        
        fmt.Println(msg)
        
    }(msg) // passing by value
    
    msg = "bye"
    
    time.Sleep() 
    
    // output 
    // hello 
    

waitgroup

ex (work application without sleep function)


import ('sync')

wc := sync.waitGroup{} // creating waitgroup 

func main () {

    msg := "hello"
    
    wg.Add(1) // adding a goroutine to waitgroup 
    
    go func(msg string) {
    
        fmt.Println(msg)
        
        wg.done()
        
    }(msg)
    
    msg = "goodbye"
    
    wg.Wait() 
    
    // output 
    // hello
}

mutex

mutex is used to lock resources

ex


import(
    'fmt'
    'sync'
)

wg := sync.WaitGreoup{} 

m := sync.RWMutex{}

counter := 0 

func main () {

    for  i := 0 ; i < 10 ; i+=1 {
    
        wg.Add(2)
        m.RLock() // read lock 
        go sayHello()
        m.Lock() // write lock 
        go increment()
    }
    wg.Wait() 
}

func sayHello () {

    fmt.Println("hello")
    m.RUnlock() // read unlock
    wg.Done()
}

func increment() {

    counter++
    m.Unlock()
    wg.Done()
}
// see video agnain to find out output 

CHANNELS

its a very unique feature and makes go standout among other

most PL were design with a single porcessing were in mind when concurrency and perellelsim came in mind . when concurrency and perellelism came they were bolted on the side and had special packages when go was designed concurrency and parallelsim were considered

channels can help pass data between go routines in a way which is safe and prevents race condition and memory sharing problem

Agenda

  • channel basic
  • restricting data flow (send / recive only channel )
  • buffered channel
  • close channels
  • for range loop with channels
  • select statement ( specially designed )

ex

 wg := sync.WaitGroup{} 
 
 func main() {
 
     ch := make(chan int) 
     // int is the data type thats going to 
     // flow through the channel are strongly typed 
     wg.Add(2)
     
     go func() {
         i := <-ch
         fmt.Prinln(i)
         wg.Done()
     }()
     
     go func() {
         ch <- 42
         wg.Done()
     }()
     
 }


// out 
// 42 

ex( async senders and recivers )

if the member of senders and recivers do not natch it is going to cause deadlock (PANIC)

only one message at the buffer at once to do otherwise we need buffered channels

ex( bideirectional routines )


    ch := make(chan int) 
    
    wg.Add(2)
    
    go func() {
       i := <- ch 
       fmt.Println(i)
       ch <- 27 
       wg.Done()
    }()
    
    go func() {
        ch <- 42 
        // passing data to the channel 
        fmt.Println(<- ch)
    }()
    
    wg.Wait()
    
    // output 
    // 42 
    // 27 

ex( directional channel )


    ch := make(chan int)
    
    wg.Add(2)
    
    go func(ch <- chan int) {     // recive only channel 
        
        i := <- ch
        
        fmt.Println(i)
        
    }(ch)
    
    go func(ch chan <- int) {    // send only 
        
        ch <- 42 
        
        wg.Done()
        
    }(ch)
    

recive data from one side and send from another

Notice we are passing bidirectional channel and aorking with a single directional channel . hear the go routine understands the needs and does the casting itself so this is a kind of polymorphic behaviour

ex ( buffer channel)

    func main () {
    
        ch := make(chan int)
        
        wg.Add(2)
        
        func ( ch <- chan int) {
            i := <- ch
            fmt.Println(i)
            wg.Done()
        }(ch)
        
        func (ch chan <- int) {
            ch <- 42
            ch <- 27 
            wg.Done()
        }(ch)
        
        wg.Wait()
        
    }

Output

fatal error all goroutines are asleep - deadlock !

as the channels are asymetric sending by 2 and reciving from 1 at a time (Buffer can solve the problem)

adding buffer to a channel

    ch := make(chan int,50) // 50 is buffer 

this channel has a internal data store which can store 50 integers

buffer are also neededwhen the senders or recivers need a littel time to process and dont want to block the other side

    ch := make(chan int , 50)
    
    wg.Add(2)
    
    go func(ch <- chan int) {
        for i:= range(ch) {
            fmt.Println(i)
        }
        wg.Done()
    }(ch)
    
    go func(ch chan <- int ) {
        ch <- 42 
        ch <- 27 
        wg.Done()
        close(ch) 
        // if we dont close the channel it will 
        // cause deadlock as it does not know 
        // when to stop reading message 
    }(ch)
    
    wg.Wait()

when we are closing the channel we have to remember that we cant send any more data one the channel is closed . so we could defer a funcion to close the channel as if we always keep it open we might have memory leak

NB. cant send data in a closed channel and we cant also detect if a channel is closed from sending routine

to know from reciving routine

    if i,ok := <- ch ; ok { // ok will be true if channel is open 
    
    }

ex( logger implementation with channel)

    const (
        logInfo = "INFO"
        logWarning = "WARNING"
        logError = "ERROR"
    )
    
    type logEntry struct {
    
        time time.Time 
        severty string 
        messge string 
    }
    
    logCh := make(chan logEntry,50)

    func main () {
        
        go logger()
        
        logCh <- logEntry{time.Now(),logInfo , "App is starting"}
        
        logCh <- logEntry{time.Now(),logInfo , "App is sutting down"}
        
        time.Sleep( 100 * time.Milisecond)
        
    }
    
    func logger () {
    
        for entry := range logCh {
        
            fmt.Println(" %v - [%v] %v \n")
            
            entry.time.Format(" %v %v ",entry.severty,entry.message)
        }
    }
    
    // output 
    // 2021-12-15 23:00:00 [INFO] App is starting 

but the logger needs to shutdown

now after the main function is done out logger function wripped down forcefully

to do it more greacefully we could do

one , with difer

    go logger() 
    
    defer func() { // executes before main exits 
        close(logch)
    }()

two , with select statement , zero memory allocation is required in this process


    var doneCh = make(chan struct{})
    
    // this is a singal only channel we cant 
    // send or recive any data with this channel
    // but we can know a message was sent or recive 

    func logger() {
    
        for { // inf loop 
            
            select {
            
                case entry := <- logch : 
                    fmt.Println()
                case <- doneCh : 
                    break  // exits from the loop and exits the channel 
                    
                // if we have a default case it will 
                // become not blocking and exits 
            }
        }
    }

now to singal done channel form the main

    func main() {
    
        go logger() 
        
        logCh <- logEntry{sth}
        
        logCh <- logEntry{sth}
        
        time.Sleep()
        
        doneCh <- struct{}{} 
        
        // struct{} is type , statuct{}{} defining a struct 
        // with no fields and then initialize struct with curly braces 
    }

    // output 
    // same as before 

Parsing JSON

golang package for json in encoding/json

for encoding json.Marshal for decoding json.Unmarshal

date type

GO JSON
bool boolean
float64 Numbers
string string
nil null
array array
map/struct Object

ex

    
    import (
        "fmt"
        "encoding/json"
    )

    type Human struct {
    
        Name  string 
        
        Age Number 
        
        Address string 
    }

func main() {

    human := Human{"Antik",23,"yemen"}
    
    humanEnc , err := json.Marshal(human) 
    
    if err != nil {
    
        fmr.Println(err)
    
    }
    
    fmr.Println(string(humanEnc))
    
    humans := []Human{
    
        Human{"Antik",23,"yemen"},
        Human{"Istiak",26,"dhaka"},
        
    }
    
    humansEnc , err := json.Marshal(humans)
    
    if err != nil {
    
        fmr.Println(err)
    }
    
    fmr.Pritnln(string(humans))
}

ex


var human1 Human 

Data := []byte(`{

    "Name" : "Arif",
    "Age" : "26",
    "Address" : "Mirpur"
}`)

err := json.Unmarshal(Data,&human1)

if err != nil {

}

fmt.Println("this struct is",human1)

var human2 []Human 

Data2 := []byte(`[
    {
        "Name" : "Arif",
        "Age" : "26",
        "Address" : "Mirpur"
    },
    {
        "Name" : "Galib",
        "Age" : "30",
        "Address" : "Faridpur"
    }
]`)

error := json.Unmarshal(Data2,&human2)

fmt.Println("the array is ",&human2)