Go Summary

This is a post of Golang Summary, for me to check out long time not writing program using Golang since 3 years ago.

1. Package

1.1. Basic

  • Every Go program is made up of packages. Which means, the basic organization unit of Go program is package, not file.
  • Programs start running in package main.
  • By convention, the package name is the same as the last element(sub-package) of the import path.
    For instance, the math/rand package comprises files that begin with the statement package rand.
  • Import packages using factored import statement. (variables declaration can also use factored declare)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package main

    import (
    "fmt"
    "math"
    )

    func main() {
    fmt.Printf("Now you have %g problems.", math.Sqrt(7))
    }

    Not using next statements, since they should be highly in a cohesion:

    1
    2
    import "fmt"
    import "math"
  • By convention, a name starts with capital letter will be exported. Eg. Pi in math package.
    This means, when using other package functions/fields, it always starts with CapitalLetters.

2. Functions

2.1. Basic

  • Two or more consecutive named function parameters share a same type, we can omit the type all but the last. x int, y int can be shorted to x, y int. Eg.

    1
    2
    3
    func swap(x, y string) (string, string) {
    return y, x
    }
  • Naked return statements should be used in only short function.

    1
    2
    3
    4
    5
    func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
    }

3. Variables

3.1. Basic

  • var statement can be a package/function level

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package main

    import "fmt"

    var i, j int = 1, 2

    func main() {
    var c, python, java = true, false, "no!" // variables type can be taken from the initializer
    fmt.Println(i, j, c, python, java)
    }
  • Short Variable can only be used inside a function using :=

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main

    import "fmt"

    func main() {
    var i, j int = 1, 2
    k := 3 // short variable
    c, python, java := true, false, "no!" // short variable

    fmt.Println(i, j, k, c, python, java)
    }

4. Types

4.1. Basic

  • Go Basic Types

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    bool

    string

    int int8 int16 int32 int64
    uint uint8 uint16 uint32 uint64 uintptr

    byte // alias for uint8

    rune // alias for int32
    // represents a Unicode code point

    float32 float64

    complex64 complex128

    The int, uint, and uintptr types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems. Normally we use int unless you have a specific reason to use a sized or unsigned integer type.

  • Zero Values: Variables will be assigned to Zero Values if no explicit initial value. Eg. 0 for numeric types, false for boolean type, "" for strings.
  • T(v) will convert value V to type T. Explicit Type Conversion is required in Go.

5. Constants

5.1. Basic

  • const is used before Constants declaration. Type can be only character/string/boolean/numeric values.

6. Flow control statements: For/If/Switch/Defer

6.1. Basic

  • For loop, the init and post statements are optional:

    1
    2
    3
    4
    sum := 0
    for i := 0; i < 10; i++ {
    sum += i
    }

    or

    1
    2
    3
    4
    sum := 1
    for ; sum < 1000; {
    sum += sum
    }
  • for is Go’s while without ;

    1
    2
    3
    4
    sum := 1
    for sum < 1000 { // just is while
    sum += sum
    }
  • infinite loop

    1
    2
    3
    4
    5
    6
    package main

    func main() {
    for {
    }
    }
  • for loop with range, can iterate the slices.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package main

    import "fmt"

    var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

    func main() {
    for i, v := range pow { // return with 2 values: index and corresponding value copy of data in that index
    fmt.Printf("2**%d = %d\n", i, v)
    }
    for i := range pow { // return only the index
    pow[i] = 1 << uint(i) // == 2**i
    }
    for _, value := range pow { // ignore the index, only need the value
    fmt.Printf("%d\n", value)
    }
    }
  • one short statement before if condition, to make statements more closed for readable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim { // only one statement is allowed here
    return v
    } else {
    fmt.Printf("%g >= %g\n", v, lim)
    }
    // After this if-else statement, v is not accessible any more
    return lim
    }
  • switch without break statement, but still can provide with a short statement before.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package main

    import (
    "fmt"
    "runtime"
    )

    func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os { // allow one short statement
    case "darwin":
    fmt.Println("OS X.")
    case "linux":
    fmt.Println("Linux.") // no need for break
    default:
    // freebsd, openbsd,
    // plan9, windows...
    fmt.Printf("%s.", os)
    }
    }
  • switch without condition is the same as switch true.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    t := time.Now()
    switch {
    case t.Hour() < 12:
    fmt.Println("Good morning!")
    case t.Hour() < 17:
    fmt.Println("Good afternoon.")
    default:
    fmt.Println("Good evening.")
    }
  • defer function will be called once its enclosed function is returned. However, deferred function parameters will be eval inside the enclosed functions immediately at its declaration position. A stack will be generated when there is defer statements in go program and will be popped out using LIFO sequence.

7. Data Structure: Pointer/Struct/Slice/Map

7.1. Basic

  • Pointer: *T is pointer used to point to type T. & operator will create a pointer that point to existing value. * operator will eval the value of the target variable.
  • Struct: collections of fields. We can use (*p).X to access struct pointer p‘s field X. To make it simple, we can just use p.X, which is implicit dereference.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package main

    import "fmt"

    type Vertex struct {
    X, Y int
    }

    var ( // use factored declare statements
    v1 = Vertex{1, 2} // has type Vertex
    v2 = Vertex{X: 1} // Y:0 is implicit
    v3 = Vertex{} // X:0 and Y:0
    )


    func main() {
    p := &Vertex{1, 2} // has type *Vertex
    fmt.Println(v1, p, v2, v3)
    }
  • [n]T means an array of type T with n size. E.g, var a [10]int, means int a[10] in Java.

  • []T means a slice of type T with dynamic size. E.g, a[1:4] create a slice, containing elements in a indexed from 1 to 3.
  • Slices does not store data, just describe a segment of the underlying array. They are more of array's references.
  • Array syntax and slice syntax: [3]bool{true, true, false} is an array, []bool{true, true, false} is an anonymous array but then referenced using a slice.
  • Slice default upper/lower boundary is array length/0. E.g

    1
    2
    3
    4
    5
    6
    var a [10]int
    // next 4 slices are equal
    a[0:10]
    a[:10]
    a[0:]
    a[:]
  • nil slice: capacity/length is 0, and there is not underlying array for this slice. E.g, var s []int.

  • Use make([]slice, len, cap) to create a slice. E.g,

    1
    2
    a := make([]int, 5)  // len(a)=5
    b := make([]int, 0, 5) // len(b)=0, cap(b)=5
  • Append elements to a slice and return with a new generated slice: func append(s []T, vs ...T) []T.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func main() {
    var s []int
    printSlice(s)

    s = append(s, 0) // append works on nil slices.
    printSlice(s)

    s = append(s, 1) // The slice grows as needed.
    printSlice(s)

    s = append(s, 2, 3, 4) // We can add more than one element at a time.
    printSlice(s)
    }
  • map[KeyType]ValueType is used to map keys to values.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    package main

    import "fmt"

    type Vertex struct {
    Lat, Long float64
    }

    var m map[string]Vertex

    func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
    40.68433, -74.39967,
    }
    var m = map[string]Vertex{ // styntax is like struct
    "Bell Labs": Vertex{
    40.68433, -74.39967,
    },
    "Google": Vertex{
    37.42202, -122.08408,
    },
    }
    var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967}, // Omit the Vertex type
    "Google": {37.42202, -122.08408},
    }
    fmt.Println(m["Bell Labs"])
    }
  • map basic operations:

    • insert/update key: m[key] = elem
    • get key: elem = m[key]
    • delete key: delete(m, key)
    • double assignment to check key existence: elem, ok := m[key] // ok will be true if exist, otherwise false
  • Function Closure: A closure is a function value that references variables from outside its body.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package main

    import "fmt"

    func adder() func(int) int { // returns a closure
    sum := 0
    return func(x int) int { // Each closure is bound to its own sum variable
    sum += x
    return sum
    }
    }

    func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
    fmt.Println(
    pos(i),
    neg(-2*i),
    )
    }
    }

8. Method and Interface

8.1. Basic

  • Method: A function with a special receiver argument.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package main

    import (
    "fmt"
    "math"
    )

    type Vertex struct {
    X, Y float64
    }

    func (v Vertex) Abs() float64 { // receiver's type declaration and method declaration must be in the same package
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }

    func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs()) // likes v receives the message of Abs, v is the receiver
    }
  • Interface: An interface type is defined as a set of method signatures. As long as some data structure implements all the methods declared int this interface type, it is an instance of this interface type.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package main

    import (
    "fmt"
    "math"
    )

    type Abser interface {
    Abs() float64
    }

    func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f // a MyFloat implements Abser, since MyFloat does have a method `Abs()`
    a = &v // a *Vertex implements Abser, since *Vertex does have a method `Abs()`

    // In the following line, v is a Vertex (not *Vertex)
    // and does NOT implement Abser.
    a = v

    fmt.Println(a.Abs())
    }

    type MyFloat float64

    func (f MyFloat) Abs() float64 {
    if f < 0 {
    return float64(-f)
    }
    return float64(f)
    }

    type Vertex struct {
    X, Y float64
    }

    func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
    }
  • Interfaces are implemented implicitly. Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement. In a word, you can define the interface/standard, everyone can implement it. This is a pluggable thought into a language.

  • Empty Interface: interfaces which has 0 methods. They can be used to process un-predictable types. Eg. fmt.Println() accept interface{} parameters.
  • Type assertion: provides access to an interface value’s underlying concrete value. using t := i.(T) to assert interface i does have a concrete type T and assign this type’s underly value to variable t.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package main

    import "fmt"

    func main() {
    var i interface{} = "hello"

    s := i.(string)
    fmt.Println(s)

    s, ok := i.(string)
    fmt.Println(s, ok)

    f, ok := i.(float64) // no panic here, since we still have ok as false and f will be nil value of float64/type T
    fmt.Println(f, ok)
    f = i.(float64) // panic: interface conversion: interface {} is string, not float64
    fmt.Println(f)
    }
  • Type switches: permits several type assertions in series.

    1
    2
    3
    4
    5
    6
    7
    8
    switch v := i.(type) {
    case T:
    // here assert v has type T
    case S:
    // here assert v has type S
    default:
    // no match; here v has the same type as i
    }

    For Example,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package main

    import "fmt"

    func do(i interface{}) {
    switch v := i.(type) {
    case int:
    fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
    fmt.Printf("%q is %v bytes long\n", v, len(v))
    default:
    fmt.Printf("I don't know about type %T!\n", v)
    }
    }

    func main() {
    do(21) // output: Twice 21 is 42
    do("hello") // output: "hello" is 5 bytes long
    do(true) // output: I don't know about type bool!
    }
  • Stringer interface in fmt package. It has a method String() string.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package main

    import "fmt"

    type Person struct {
    Name string
    Age int
    }

    func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
    }

    func main() {
    a := Person{"Arthur Dent", 42}
    z := Person{"Zaphod Beeblebrox", 9001}
    fmt.Println(a, z) // output: Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
    }
  • error interface in fmt package. it has a method Error() string. This is the so-called customized exception.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package main

    import (
    "fmt"
    "math"
    )

    type ErrNegativeSqrt float64 // we can wrap the value itself into this error and print info related to this obj inside the Error() method, this is a good practice
    func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
    }
    func Sqrt(x float64) (float64, error) {
    if x < 0 {
    return 0, ErrNegativeSqrt(x)
    }
    return math.Sqrt(float64(x)), nil
    }

    func main() {
    fmt.Println(Sqrt(2)) // output: 1.4142135623730951 <nil>
    fmt.Println(Sqrt(-3)) // output: 0 cannot Sqrt negative number: -3
    }

9. Concurrency

9.1. Basic

  • Goroutine: lightweight thread managed by the Go runtime. go f(x, y, z) starts a new goroutine running function f(x,y,z). The evaluation of parameters happens immediately, but execution happens later.
  • Chanel: typed conduit through which you can send and receive values with the channel operator, <-.
    The data flows in the direction of the arrow.

    1
    2
    3
    ch <- v    // Send v to channel ch.
    v := <-ch // Receive from ch, and
    // assign value to v.

    Create a channel before using it: ch := make(chan int).

  • Range/Close a channel, use close(ch) from producer side to close a channel to tell the receiver no more items produced.
    v, ok := <-ch, the ok will be false if ch is already closed.
    for i := range c will get value from channel continuously until it’s closed

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package main

    import (
    "fmt"
    )

    func fibonacci(n int, c chan float64) {
    x, y := 0.0, 1.0
    for i := 0; i < n; i++ {
    c <- x
    x, y = y, x+y
    }
    close(c)
    }

    func main() {
    c := make(chan float64, 100)
    go fibonacci(cap(c), c)
    for i := range c {
    fmt.Println(i)
    }
    }
  • select statement: select statement lets a goroutine wait on multiple communication operations. It blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package main

    import "fmt"

    func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for { // this is a inf loop
    select { // implicitly we do have a goroutine here, it waits on multiple channels
    case c <- x:
    x, y = y, x+y
    case <-quit:
    fmt.Println("quit")
    return // this is quit condition for this inf loop
    }
    }
    }

    func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() { // 1st goroutine
    for i := 0; i < 10; i++ {
    fmt.Println(<-c)
    }
    quit <- 0 // removing this line will get this program stuck
    }()
    fibonacci(c, quit) // 2nd goroutine, block next all statements
    quit <- 1 // this statement will never be executed since the above statement blocks if `quit <- 0` is removed
    }
  • Time elapsed channel: time.Tick and time.After.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package main

    import (
    "fmt"
    "time"
    )

    func main() {
    tick := time.Tick(1000 * time.Millisecond) // every 1 second, get a value from this channel
    boom := time.After(5000 * time.Millisecond) // after 5 second, get a value from this channel
    for {
    select {
    case x := <-tick:
    fmt.Println("tick.", x)
    case x := <-boom:
    fmt.Println("BOOM!", x)
    return
    default:
    // fmt.Println(" .")
    time.Sleep(500 * time.Millisecond)
    }
    }
    }

    Output:

    1
    2
    3
    4
    5
    tick. 2009-11-10 23:00:01 +0000 UTC m=+1.000000001
    tick. 2009-11-10 23:00:02 +0000 UTC m=+2.000000001
    tick. 2009-11-10 23:00:03 +0000 UTC m=+3.000000001
    tick. 2009-11-10 23:00:04 +0000 UTC m=+4.000000001
    BOOM! 2009-11-10 23:00:05 +0000 UTC m=+5.000000001
  • range/close or select/quit channel or Tick/After to do the goroutine synchronization using channel.

  • sync.Mutex has Lock/Unlock which can also provide shared-memory mechanism synchronization.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package main

    import (
    "fmt"
    "sync"
    "time"
    )

    // SafeCounter is safe to use concurrently.
    type SafeCounter struct {
    v map[string]int
    mux sync.Mutex
    }

    // Inc increments the counter for the given key.
    func (c *SafeCounter) Inc(key string) {
    c.mux.Lock()
    // Lock so only one goroutine at a time can access the map c.v.
    c.v[key]++
    c.mux.Unlock()
    }

    // Value returns the current value of the counter for the given key.
    func (c *SafeCounter) Value(key string) int {
    c.mux.Lock()
    // Lock so only one goroutine at a time can access the map c.v.
    defer c.mux.Unlock()
    return c.v[key]
    }

    func main() {
    c := SafeCounter{v: make(map[string]int)}
    for i := 0; i < 1000; i++ {
    go c.Inc("somekey")
    }

    time.Sleep(time.Second)
    fmt.Println(c.Value("somekey"))
    }

10. Build

10.1. use golang docker image to build

Use golang:1.9 to build go program
https://github.com/docker-library/docs/tree/master/golang#compile-your-app-inside-the-docker-container

1
2
3
4
5
6
7
APPNAME=passport
BUILD_PATH=/usr/src/${APPNAME}
GOLANG_IMG="golang:1.9"

docker run --rm -v "$PWD":"${BUILD_PATH}" -w ${BUILD_PATH} -e GOPATH="${BUILD_PATH}" -e GOBIN="${BUILD_PATH}/bin" -e CGO_ENABLED=0 -e GOOS=linux -e GOARCH=amd64 golang:1.9 go get -v && go build -a -installsuffix cgo \
-ldflags "-s -w" \
-o "${BUILD_PATH}/${APPNAME}" .


References:

0%