Google’s Go Essentials For Node.js / JavaScript Developers

  • 2019-04-06 01:27 AM
  • 203

As an advanced JavaScript developer, The more I work with JavaScript the more I understand the advantages of a statically typed language.

Paradoxically, it is easier to make TypeErrors in JavaScript than in other languages I have used. When you do work on the back-end, it is unacceptable. On the front end, when you are a little bit serious about your work, you notice how so many JS developer do NOT understand the typing system in JavaScript because it is not forced on them. That’s why there is a lot of garbage JS code in the world.

JavaScript is HARD when you get serious about understanding it under the hood. So many (kind of hidden) complex concepts you have to understand in order to make sense of the errors in your code (or why it’s working but that’s not the correct way to do it…).

JavaScript is fascinating but sometimes you want to try something else and broaden your horizon.

This article will be a brain dump of all I’ve learned so far about Google’s Go language.

I assume you have installed the Go binaries on your computer.

Variables

package main

import "fmt"

func main() {
    _// you MUST use a declared variable otherwise compilation error_

    var toto int8 = 123
    fmt.Println(toto)

    var tito = 123
    fmt.Println(tito)

    _// variable declaration with type inference_
    toti := 123
    fmt.Println(toti)

    _// variable declaration (implicitly initialized to zero-value,// for numeric types = 0)_
    var tata int
    fmt.Println(tata)

    _// variable assignment_
    tata = 951
    fmt.Println(tata)
}

Number Types

package main

import "fmt"

func main() {
    _// Go does not convert types automatically_
    _// need to explicitly convert them_
    var (
        i int8    = 20
        f float32 = 5.6
    )
    fmt.Println(i + int8(f+1.9))

    var (
        j int32 = 456
        k int64 = 987654
    )
    fmt.Println(int64(j) + k)

    _// byte is an alias for uint8_
    _// no need to convert uint8 to byte because same_
    var (
        l byte  = 123
        m uint8 = 45
    )
    fmt.Println(l + m)

    _// int is an alias for int32 or int64, // dedpending on your mqchine's integer value_
    var (
        n int32 = 324
        o int   = 84529899
    )
    fmt.Println(int(n) + o)

    _// uint is an alias for uint32 or int64_
    var (
        p uint   = 999
        q uint64 = 9999
    )
    fmt.Println(p + uint(q))

    _// float operations do not produce an exact result after n decimals// like in most languages_
    myFloat := 1.000
    myFloat2 := .999
    fmt.Println(myFloat - myFloat2)

    _// arithmetic operations_
    fmt.Printf("%d + %d = %d \n", 25, 39, 25+39)
    fmt.Printf("%d - %d = %d \n", 25, 39, 25-39)
    fmt.Printf("%d * %d = %d \n", 25, 39, 25*39)
    fmt.Printf("%d / %d = %v \n", 25, 39, 25/39)
    fmt.Printf("%d %% %d = %v \n", 25, 39, 25%39)

    _// constants_
    const goldenRatio float64 = 1.6180327868852
    fmt.Printf("The golden ration approximately %f \n", goldenRatio)
    fmt.Printf("The ration truncated to the 3rd decimal is %.3f \n", goldenRatio)

    _// formatted printing for numeric types_
    fmt.Printf("decimal is %d \n", 99)
    fmt.Printf("binary is %b \n", 99)
    fmt.Printf("unicode reference is %c \n", 99)
    fmt.Printf("hexadecimal is %x \n", 99)
    fmt.Printf("scientific notation of goldenRatio is %e \n", goldenRatio)
}

String Types

package main

import "fmt"

func main() {
    _// zero-value for strings is an empty string ""_
    var str string
    fmt.Println("\"" + str + "\"")

    str = "This is a string."
    fmt.Println(str)

    str2 := "Another string."
    fmt.Println(str2)

    _// raw string literalss with back ticks_
    _// can be written on multiple lines and no escapes_
    str3 := `
        Raw string in the building.
        And another line.
    `
    fmt.Println(str3)

    str4 := "Dunya nzuri = "
    str5 := "美麗的世界"
    str6 := str4 + str5
    fmt.Println(str6)

    _// In Go, strings are immutable sequences of bytes_
    _// you can access each byte_
    str7 := "mazoezi"
    b1 := str7[0]
    b2 := str7[1]
    fmt.Println(str7, "\n\t", b1, "=", string(b1), b2, "=", string(b2))

    _// substrings_
    s1 := str7[0:2]
    s2 := str7[2:4]
    s3 := str7[:3]
    s4 := str7[3:]
    fmt.Printf("%s \t %s \t %s \t %s \n", s1, s2, s3, s4)

    _// length of string_
    fmt.Println(str7, " = ", len(str7), " characters")

    _// single character = rune -> numeric type, sane as int32_
    _// can be converted to a string_
    var r rune 
    _// single qutoes_
    r = '✖'         _// same as r = 10006_
    fmt.Println("This is a rune : ", r, " which in string = ", string(r))
}

If and For Statements

package main

import "fmt"

func main() {
    a := 27

    _// no parentheses surrounding the condition_
    _// the body of the if statement MUST be surrounded with {} // no matter what_
    _// no truthy values_
    if a > 25 {
        fmt.Println("a is greater than 25")
    } else {
        fmt.Println("a is less than 25")
    }

    b := 546

    _// if statement have block scope_
    if b == 546 {
        _// c does not exist outside of the if_
        c := 54
        fmt.Println(b + c)
    }

    _// can declare a variable available ONLY in if and else block_
    if d := 44; b < 25 {
        fmt.Println("a is greater than 25", d)
    } else {
        fmt.Println("a is less than 25", d)
    }

    _// for loop, no parentheses around signature_
    e := 2
    for index := 0; index < 10; index++ {
        if index+e == 2 {
            continue
        }
        if index > 8 {
            break
        }
        fmt.Println("index =", index)

    }

    _// equivalent of while statement_
    f := 0
    for f < 6 {
        fmt.Println("f =", f)
        _// don't forget to have smth allowing to get out of the loop_
        f++
    }

    _// infinite loop_
    g := 0
    for {
        fmt.Println("g =", g)
        g++
        _// to stop it at some point_
        if g > 30 {
            break
        }
    }

    _// for range loop_

    h := "this is great!"
    for k, v := range h {
        fmt.Println("offset (position) =",k,", value as rune =", v, ",value as string = ", string(v))
    }

    _// logical operators_
    fmt.Printf("%t && %t is %t \n", true, false, true && false)
    fmt.Printf("%t || %t is %t \n", true, false, true || false)
    fmt.Printf("!%t is %t \n", true, !true) 
}

Functions

package main

import "fmt"

func main() {
    addNumbers(353454, 99999)
    addNumbers(353, 9999)
    addNumbers(3554, 99)

    a := addInts(99, 1)
    fmt.Println("a = ", a)

    a = addInts(9, 675)
    fmt.Println("a = ", a)

    div, remainder := divAndRemainder(57, 7)
    fmt.Println(div, remainder)

    _// use underscore to ignore a returned value_
    div, _ = divAndRemainder(57, 7)
    fmt.Println(div)

    _, remainder = divAndRemainder(57, 7)
    fmt.Println(remainder)

    divAndRemainder(57, 7)

    _// in Go, all functions calls are done by value// (exceptions, see later)_
    _// a copy of input argument variables is passed o the function_
    y := 5
    arr := [2]int{45, 99}
    s := "olo"
    doubleFail(y, arr, s)
    fmt.Println("outside doublefail", y, arr, s)
}

_// where function is placed does not matter_
_// no overloading of function w/ different input parameters_
func addNumbers(a int, b int) {
    fmt.Println(a + b)
}

func addInts(c int, d int) int {
    return c + d
}

_// multiple returns_
func divAndRemainder(e int, f int) (int, int) {
    return e / f, e%f
}

func doubleFail(a int, arr [2]int, s string)  {
    a = a * 2
    for index := 0; index < len(arr); index++ {
        arr[index] *= 2
    }
    s = s + s
    fmt.Println("in doublefail", a, arr, s)
}

Pointers

_// pointers are used in C to simulate arrays and strings_
package main

import "fmt"

_// pointer as input parameter_
func setTo10(pointerToInt *int) {
    *pointerToInt = 10
}

func setTo10Fail(pointerToInt *int) {
    fmt.Println("#setTo!(Fail pointer passed as argument =", pointerToInt)
    _// will not affect the original pointer because passed by value_
    pointerToInt = new(int)
    fmt.Println("#setTo!(Fail reassignment =", pointerToInt)

    _// set the value in memory to 10_
    *pointerToInt = 10
}

func main() {
    a := 10
    _// & = reference / pointer to variable "a"_
    _// the value of b is the location where a is stored_
    b := &a
    _// c is a copy of "a" at a given time, // they are independent of each other after the first assignment_
    c := a
    fmt.Println(a, b, *b, c)

    a = 20
    _// to see the value inside the memory location use * // (de-reference the pointer and get to the value)_
    fmt.Println(a, b, *b, c)

    _// dereference the pointer and assign a value in memory_
    _// therefore the value of "a" also changes_
    *b = 30
    fmt.Println(a, b, *b, c)

    c = 40
    fmt.Println(a, b, *b, c)

    _// zero-value for a pointer is nil (absence of value)_
    var d *int
    fmt.Println("value of d =", d)
    _// cannot read or write value of a nil pointer_
    _// fmt.Println(*d)   // throws a panic_

    e := new(int)
    _// new keyword makes a pointer for the type_
    fmt.Println("pointer to e =", e)
    _// new also allocates memory, here to the zero-value forint type// therefore no panic_
    fmt.Println("value of e =", *e)

    f := 20
    fmt.Println("value of f =", f)
    _// we pass a pointer to f into that function_
    setTo10(&f)
    fmt.Println("value of f after setTo10 =", f)

    g := 30
    fmt.Println("pointer to g =", &g)
    fmt.Println("value of g =", g)
    _// on Go, variable in function calls are passed by VALUE_
    setTo10Fail(&g)
    fmt.Println("pointer to g after #setTo10Fail =", &g)
    _// you CANNOT change the pointer of a variable passed_
    fmt.Println("value of g after #setTo10Fail =", g)
}

Arrays

package main

import "fmt"

func main() {
    _// zero-value of arrays is an array// of specified length of zero-values of the type inside the array_
    var myArrInt [4]int
    fmt.Println(myArrInt)

    myArrInt[0] = 12
    myArrInt[1] = 23
    myArrInt[2] = 34
    myArrInt[3] = 45
    fmt.Println(myArrInt)

    _// one-line array assihnement in a composite literal expression_
    _// the length of the array is part of its type definition_
    myArrInt = [4]int{111, 222, 333, 444}
    fmt.Println(myArrInt)

    myArrStr := [4]string{"titi", "tooi", "tatu", "teti"}
    fmt.Println(myArrStr)

    _// iterate over an array_
    for i, val := range myArrInt {
        fmt.Printf("At index %d = %d \n", i, val)
    }

    _// slice of an array_
    myArrStr2 := myArrStr[:2]

    _// use _ to ignore the index variable_
    for _, val := range myArrStr2 {
        fmt.Println(val)
    }
    _// use of arrays is limited, slices are more flexible_

}

Slices and Maps

package main

import "fmt"

func main() {
    _/*
      SLICE = growable sequence of values of a single specified type
      the size is not part of the type definition
    */_
    _// define a slice in a composite literal expression_
    myFiboSlice := []int{0, 1, 2, 3, 5, 8, 13}
    fmt.Println("myFiboSlice is", myFiboSlice)

    _// create a slice from another slice = slice expression_
    myFiboSlice2 := myFiboSlice[1:4]
    fmt.Println("myFiboSlice2 is", myFiboSlice2)

    _// carefully with subslices because// they point to the same location in memory as the original slice_
    myFiboSlice[2] = 00
    fmt.Println("\n myFiboSlice is", myFiboSlice)
    _// slices are reference types, behave like pointers_
    fmt.Println("myFiboSlice2 after modifying myFiboSlice is", myFiboSlice2)

    _// zero-value for slice is nil slice (no value in slice)_
    titiSlice := []string{"titi_one", "titi_two", "titi_three"}
    var totoSlice []string
    fmt.Println("totoSlice is", totoSlice)
    fmt.Println("length of totoSlice is", len(totoSlice))

    _// assigning slice to another slice makes them share same location// in memrory_
    totoSlice = titiSlice
    fmt.Println("totoSlice is now", totoSlice)
    titiSlice[0] = "titi_zero"
    fmt.Println("after modifying titiSlice, totoSlice is now", totoSlice)

    _// this behavior also happens in functions_
    modifySlice(titiSlice)
    fmt.Println("after modifying titiSlice in a function, totoSlice is now", totoSlice)

    _// define a slice filled with wero-values of the type specified_
    _// last argument is the capacity of the underlying array_
    s1 := make([]int, 5, 20)

    _// copy a slice from another slice with the copy built-in function// "copy in slice s1 the elements of slice myFiboSlice_
    copy(s1, myFiboSlice) 
    fmt.Println("\ns1 is", s1)

    _// append built-in function -> returns a new slice_
    _// increases the length of the slice_
    s2 := append(s1, 21, 34, 55)
    fmt.Println("s2 is", s2)

    _// append a slice to another slice_
    s3 := []int{111, 222, 333}
    _// notice the ... after the s3 identier to spread the elements in it_
    s3 = append(s2, s3...)
    fmt.Println("s3 is", s3)

    _// deleting element at offset 6 (7th) from slice (the easy way)_
    s3 = append(s3[:6], s3[7:]...)
    fmt.Println("s3 is", s3)

    _// deleting from slice, the more involved way (see the function below)// it was tough to find a working algorithm !_
    s3 = deleteItemFromSliceAtIndex(s3, 3)
    fmt.Println("s3 is", s3)

    _// slices are based on an underlying array // for which you can specify the capacity // (number of spots to allocate in memory)_
    _// here the slice is initialized with 5 zero-value spots // but the underlying array has 100 spots in case we append._
    _// This allows to avoid to copy and create a new underlying array // every time we append more than the length of the slice_
    s4 := make([]int, 5, 100)
    fmt.Println("s4 is", s4)
    fmt.Println("length of s4 is", len(s4))
    fmt.Println("capacity of s4 is", cap(s4))

    _// make a slice of bytes out of a string_
    hello := "李先生你好"
    myByteSlice := []byte(hello)
    fmt.Println("\nmyByteSlice is", myByteSlice)

    myRuneSlice := []rune(hello)
    fmt.Println("\nmyRuneSlice is", myRuneSlice)

    _// multidimensional slice_
    s5 := []int{84, 64, 44}
    s6 := []int{42, 32, 22}
    s7 := [][]int{s5, s6}
    fmt.Println("\ns7 is", s7)

    _/********** MAPS ****************/_

    _// associate value of single data type to value of another data type// collection of key / value pairs (key not restricted to a string)_
    _// maps are unordered_

    myMap := make(map[string]string)
    myMap["name"] = "GOTO"
    myMap["firstname"] = "Florian"
    myMap["occupation"] = "Software Engineer"
    myMap["native_language"] = "French"
    fmt.Printf("\n%v\n", myMap)

    _// access value in map_
    fmt.Printf("The name is %v\n", myMap["name"])

    _// if no value on a key return zero-value for the type of value_
    fmt.Printf("The age is %v\n", myMap["age"])

    _// make sure a key is in the map = comma ok idiom_
    _// v = value associated w/ existing key_
    _// ok = boolean is key in map_
    if v, ok := myMap["isBillionaire"]; ok {
        fmt.Println("isBillionaire in map =", v)
    } else {
        _// ok == false_
        fmt.Println("isBillionaire in map =", ok)
    }

    _// map literal declaration (composite literal expression)_
    worldMap := map[int]string{
        _// every line must end with a comma_
        1: "nimefurahi kukuona",
        2: "приємно бачити вас",
        3: "तुम्हें देखकर अच्छा लगा",
        4: "ስለተያየን ደስ ብሎኛል",
        5: "ดีใจที่ได้พบคุณ",
    }

    _// iterate over a map - order of iteration is random_
    for keyInMap, valueInMap := range worldMap {
        fmt.Println(keyInMap, "=", valueInMap)
    }

    _// delete value from map (built-in function)_
    delete(worldMap, 2)
    fmt.Println("worldMap :", worldMap)

    _// same as slices, maps are passed by reference_

    _// nil map_
    var tMap map[string]int
    _// writing to zero-valued map will make the program panic_
    _// tMap["toto"] = 12345678_
    fmt.Println("tMap :", tMap)
    fmt.Println("length of tMap :", len(tMap))

    _// delete an element in map_
    sport := map[string]string{"yoyo": "ok", "ping pong": "great"}
    fmt.Println("sport :", sport)
    delete(sport, "yoyo")
    fmt.Println("sport :", sport)

    _// to make sure that you delete an existing pair in the map_
    langs := map[string]string{
        "ES6+":         "great",
        "Go":           "cool",
        "TypeScript":   "ok",
        "Python":       "over hyped but nice",
        "Bash":         "necessary",
        "HTML5":        "necessary",
        "CSS":          "necessary",
        "Elm":          "niche",
        "Java":         "no comments...",
        "Rust":         "to assess",
        "Web Assembly": "who knows",
    }
    fmt.Println("lang :", langs)
    if _, ok := langs["assembly"]; ok {
        delete(langs, "assembly")
    }
    fmt.Println("lang :", langs)

}

func modifySlice(s []string) {
    s[1] = "one"
    fmt.Println("\n", s)
}

func deleteItemFromSliceAtIndex(s []int, index int) []int {
    temp := make(map[int]int, len(s))
    _// convert slice to map_
    for i, v := range s {
        if i == index {
            continue
        }
        temp[i] = v
    }

    newSlice := make([]int, len(temp))

    for i := 0; i < len(temp); i++ {
        if i == index {
            _// comma dot idiom = check if key in map_
            value, ok := temp[i+1]

            _// check that next index exists_
            if ok && i <= len(temp) {
                newSlice[i] = value
                continue
            }
        }

        if i > index {
            newSlice[i] = temp[i+1]
            continue
        }

        newSlice[i] = temp[i]
    }

    return newSlice
}

I won’t go into concurrency and the like (Goroutines, channels…) here which are more advanced topics that make Go apart from other languages.

Keep learning new things, never stops !

Originally published by Florian GOTO at https://medium.com

Learn More

Go: The Complete Developer’s Guide (Golang)
Learn How To Code: Google’s Go (golang) Programming Language
Web Development w/ Google’s Go (golang) Programming Language
Build Realtime Apps | React Js, Golang & RethinkDB
Google Golang Masterclass
Advanced Google’s Go (golang) Programming Course

Suggest