---
# System prepended metadata

title: golang basic
tags: [go]

---

# :memo: Golang

## Naming Rules and Conventions

### Files

- 全小寫 snake case 用 _ 將多個字串起來
- 組合字用 _ 串起來 ex: hello_world
- 開頭如果是 `.` or `_` 會被 go tool 略過
- 檔名尾是 `_test.go` 只給 go test tool 用的 (Unit test)

### Functions and Methods

- camel case
- 開頭 Upper case 表示 export 就是 public 可以被外面看到
    - writeToDB // unexported, only visible within the package
    - WriteToDB // export, visible within the package

### Variables

- camel case
- 略縮全大寫 ex: serveHTTP, userID
- 用單一字母 i, j, k
- 複數詞表示 collection (slice, array) ex: songs, albums
- 在同一 repo 使用相同的命名風格
- 避免重複 package name
    - log.Info() // good
    - log.logInfo() // bad

## Syntax

### declare and assignment

```go=
var a, b, c int
```
```go=
var (
    a int
    b int
    c int
)
```
```go=
var (
    a, b, c int
    d, e, f bool
)
```
```go=
var a, b, c int = 1, 2, 3
```
```go=
var a int = 1
var b int = 2
var c int = 3
```
```go=
var (
    a int = 1
    b int = 2
    c int = 3
)
```
```go=
var (
    a, b, c int = 1, 2, 3
)
```
```go=
只有在 func 能用語法糖，不寫型別
func main() {
    a := 1
    b := 2
    c := 3
    d, e, f := true, 1, 30
}
```

### Flow

#### for

> Go裡沒有`++i`, 也不能透過`i++`賦值給其他的變數

```go=
for i := 1; i <= 10; i++ {
    fmt.Println(i)
}
```

```go=
// while (go 沒有 while)
j := 1
for j <= 10 {
    fmt.Println(j)
    j++
}
```

```go=
// do while (go 沒有 do while)
k := 1
for {
    fmt.Println(k)
    if k == 10 {
        break
    }
    k++
}
```

#### if
```go=
if 7%2 == 0 {
    fmt.Println("7 is even")
} else {
    fmt.Println("7 is odd")
}
if 8%4 == 0 {
    fmt.Println("8 is divisible by 4")
}
if num := 9; num < 0 {
    fmt.Println(num, "is negative")
} else if num < 10 {
    fmt.Println(num, "has 1 digit")
} else {
    fmt.Println(num, "has multiple digits")
}
```

#### switch

> switch 和其它語言不同，它不需要寫 break 這個關鍵字

```go=
// match
i := 2
fmt.Print("Write ", i, " as ")
switch i {
case 1:
    fmt.Println("one")
case 2:
    fmt.Println("two")
case 3:
    fmt.Println("three")
}
```

```go=
// match
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
    fmt.Println("It's the weekend")
default:
    fmt.Println("It's a weekday")
}

```

```go=
t := time.Now()
switch {
// expression like if else
case t.Hour() < 12:
    fmt.Println("It's before noon")
default:
    fmt.Println("It's after noon")
}
```

## Types & Keywords

- [go types](https://golang.org/ref/spec#Numeric_types)

| type | 的預設值 | value or ref
|---|---|---|
| integer | 0 | value |
| floating | 0.0 | value |
| bool | false | value |
| string | "" | value |
| interface | nil | ref |
| slice | nil | ref |
| channel | nil | ref |
| map | nil | ref |
| pointer | nil | ref |
| function | nil | ref |
| array | fileds zeroed | value |
| struct | fileds zeroed | value |

### Nil

> :bulb: 到底在 golang 中 nil 是什麼東西？
> - nil 意思
    - 意思是無
    - 意思是 zero
    - 意思是零值
> - 如果 Declare 一個變數不進行賦值，那麼就會有個默認零值
    - ex: var a int (a = 0)
    - ex: var b *int (a = nil)
    - ex: var c chan int (c = nil)
    - ex: var d map[string]string (d = nil)
    - ex: var e interface{} (e = nil)
    - ex: var f struct { A int B string C *int} (A=0, B="", C=nil)
> - 所以 nil 是 **預定義** 的標識符，代表了 pointer, function, interface, map, slice, channel 的零值
> - 當 func 需要 pointer, function, interface, map, sliace, channel 時，如果不需要帶入內容，那可以直接給 nil
>    - ex: httpReq, _ := http.NewRequest(http.MethodGet, "https://postman-echo.com/get", nil) <- 這邊的 nil 是 io.Reader 表示沒有需要傳入內容
>    - ex: 需要傳入 key -> value 的 map 時 (map[string]string)，也可以使用 nil ，表示沒有對應的內容

```go=
// from source code /src/builtin/builtin.go
type Type int
var nil Type
```

> 它不是關鍵字，只是個變數，所以你可以改變 nil 的值，只是千萬不要這樣做

#### Nil type and compare

```go=
package main

import "reflect"

func main() {
    var test any // interface{} 宣告未初始為 (interface{})(nil)
    println(test == nil) // true, 因為 nil 沒有型別，所以是一樣的
    println(test == (*string)(nil)) // false

    test = (*string)(nil)
    println(test == nil) // false, 因為 test 是 (*string)(nil)
    println(test == (*string)(nil)) // true
    println(reflect.TypeOf(test).String()) // (*string)
}
```

### Constant

- const 只能是以下類型
    - bool
    - int, float, double
    - string
- 可透過 const () group 賦值
- 可以 **只定義不用**
- 如果 const group 中，如果不指定 type, value 則會和上一行非空值相同
- **iota** 是一個會自動被 compiler 累加的 const，而且它可以進行運算

```go=
const monday int = 1
const tuesday = "2"

// group of const
const (
    one   = 1
    two   = 2
    three // const group 中沒定義值的話，和上一行一樣
    four  // 也是 2
)

const (
    A = iota // 一個會被 compiler 自動累加的 const 從 0 開始 (int type)
    B
    C
    D
)

const (
    E = iota*2 + 1 // 可以參與計算
    F
    G
    H
)

// baseon golang 的 ENUM
type memberLv int

const (
    memberV1 memberLv = iota + 1 // 結合 type 更能表現實際意義
    memberV2
    memberV3
    memberV4
    memberV5
)

func main() {
    fmt.Println(monday, tuesday)
    fmt.Println(three, four)
    fmt.Println(A, B, C, D)
    fmt.Println(E, F, G, H)
    fmt.Println(memberV5)
}
```

### String

```go=
var s string = ""
var (
    s string = ""
)
s := ""
```

```go=
s1 := "hello"
s2 := "world"
fmt.Println(s1+s2) // 透過 + 來連結字串
```

```go=
// substring
input := "this is hello world"
fmt.Println(input[:4]) // from 0 get 3 char -> this
fmt.Println(input[:len(input)]) // from 0 get full length of string -> this is hello world
fmt.Println(input[:]) // 同上，取全部

// substring
idx := strings.Index(input, "hello") // 傳回 hello 出挸的第一個位置，都沒找到回傳 -1
if idx != -1 {
    fmt.Println(idx, input[:idx]) // [0:8] result: 8 "this is "
}
```

### Bool

```go=
var a bool = true
var (
    a bool = true
)
a := true
```

### Array

> golang 中的 array 的變數不是個「address」真的是個變數 [看這篇](https://dwdraju.medium.com/deep-dive-into-pointers-arrays-slice-309a843c63ad)

```go=
var a [5]int
fmt.Println(a)

a[4] = 100
fmt.Println(a)
fmt.Println(a[4])

fmt.Println(len(a))

b := [5]int{
    1,
    2,
    3,
    4,
    5,
}
fmt.Println(b)

var two [2][3]int
for i := 0; i < 2; i++ {
    for j := 0; j < 3; j++ {
        two[i][j] = i + j
    }
}
fmt.Println(two)
```

### Slice

> 和 array 不同於，slice 透過 make 建立空間，且 slice 是可變的，可以動態改變長度，可透過 append 把元素加進去
> 
> 直接把 slice 看成是一種 struct 由 head pointer to array, len length (可視), cap length (實際大小) 組成
> 
> 透過 append() 這種會改變它的 struct {ptr, len, cap} 在傳遞變數到 func 要特別注意兩種方式
> 1. 傳「值」那就要回傳值，再把本來的 caller 的值蓋掉
> 
>     ```go=
>     package main
> 
>     import "fmt"
> 
>     func modify(s []string) []string {
>         s = append(s, "c")
>         return s
>     }
> 
>     func main() {
>         mainS := []string{"a", "b"}
>         mainS = modify(mainS)
>         fmt.Printf("%+v", mainS)
>     }
>     ```
> 
> 2. 傳「址」就不用回傳值，func 進從 append 改變 slice struct 的結構
> 
>     ```go=
>     package main
> 
>     import "fmt"
> 
>     func modify(s *[]string) {
>         *s = append(*s, "c")
>     }
> 
>     func main() {
>         mainS := []string{"a", "b"}
>         modify(&mainS)
>         fmt.Printf("%+v", mainS)
>     }
>     ```

```go=
s := make([]string, 3) // 先建出三格 string 的空間
s2 = append(s, "A") // 加入第四格並讓 s2 指向這個 slice 而 s 不變
```
```go=
s := []string {} // 建出空的 string slice 空間
s2 = append(s, "A") // 加入第二格並讓 s2 指向這個 slice 而 s 不變
```
```go=
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Printf("%#v  %d\n", s, len(s))

s_empty := []string {
    "d",
    "e",
    "f",
}
fmt.Printf("%#v  %d\n", s_empty, len(s_empty))

s = append(s, "d")
s = append(s, "e")
fmt.Printf("%#v  %d\n", s, len(s))

t := make([]string, len(s))
copy(t, s)
fmt.Printf("%#v  %d\n", t, len(t))
u := t[2:4]
fmt.Printf("%#v  %d\n", u, len(u))
v := t[:4]
fmt.Printf("%#v  %d\n", v, len(v))
w := t[3:]
fmt.Printf("%#v  %d\n", w, len(w))

twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
    twoD[i] = make([]int, i + 1)
    for j := 0; j < i; j++ {
        twoD[i][j] = i + j
    }
}
fmt.Println(twoD)
```
```go=
func update_array_pass_by_value(array [5]int) {
	array[1] = 5566
	fmt.Println("update_array_pass_by_value:", array)
}

func update_array_pass_by_pointer(array *[5]int) {
	(*array)[1] = 5566
	fmt.Println("update_array_pass_by_pointer:", *array)
}

func update_slice_pass_by_value(slice []int) {
	slice[1] = 7788
	fmt.Println("update_slice_pass_by_value:", slice)
}

func update_slice_pass_by_pointer(slice *[]int) {
	(*slice)[2] = 9191
	fmt.Println("update_slice_pass_by_pointer", *slice)
}

func main() {
	// array test
	a := [5]int{1,2,3,4,5}
	b := a
	fmt.Println(a, b)
	a[0] = 6 // change a to see b is changed ?
	fmt.Println(a, b) // no because b is "clone" from a, not "reference" from a

	// slice test
	c := []int{6,7,8,9,10}
	d := c
	fmt.Println(c, d)
	c[0] = 100 // change c to see d is chaned ?
	fmt.Println(c, d) // yes because d is "reference" from c, not "clone" from c

	update_array_pass_by_value(a)
	fmt.Println(a, b) // no change a and cloned b data

	update_array_pass_by_pointer(&a)
	fmt.Println(a, b) // a has changed, because pointer of the array is passed to function, function use the pointer dereferecing to array and change the data

	update_slice_pass_by_value(c)
	fmt.Println(c, d) // c and d has changed, because c,d,slice (is pointer) reference to the same data in memory

	update_slice_pass_by_pointer(&c) // c and d has changed, use double dereferencing to update the same data in memory. *slice -> *c -> []int{}
	fmt.Println(c, d)
}

```

> slice 超雷 [參考](https://blog.golang.org/slices-intro#TOC_6.)
> len = 可視
> cap = 真實存在的 array size
> 當真實存在的 size 可以被 append 時，不會建另外一個 array 因此若每兩個 pointer 參考時會被互蓋

```go=
a := make([]int, 0, 10) // 做出 可視 len = 0 但 空間為 10 的 array
fmt.Printf("a -> %#v %p len:%d cap:%d\n", a, a, len(a), cap(a))

b := append(a, 1,2,3)  // 空間放的下，B 可視 len = 3 空間還是 10 的 array
fmt.Printf("b -> %#v %p len:%d cap:%d\n", b, b, len(b), cap(b))

_ = append(a, 99, 88, 77) // 從 A 的位址開始往後蓋 3 格，對 A 沒影響，但影響了 B 的 可視 len
fmt.Printf("a -> %#v %p len:%d cap:%d\n", a, a, len(a), cap(a))
fmt.Printf("b -> %#v %p len:%d cap:%d\n", b, b, len(b), cap(b))

/*
a -> []int{} 0xc000020050 len:0 cap:10
b -> []int{1, 2, 3} 0xc000020050 len:3 cap:10
a -> []int{} 0xc000020050 len:0 cap:10
b -> []int{99, 88, 77} 0xc000020050 len:3 cap:10
*/

fmt.Println("======================")

aa := make([]int, 0, 2) // 做出 可視 len = 0 空間為 2 的 array
fmt.Printf("aa -> %#v %p len:%d cap:%d\n", aa, aa, len(aa), cap(aa))
bb := append(aa, 5, 6, 7) // 要 append 時發現 空間為 2 不夠 copy ，因此做出另外一個 array = cap*2 = 4 的空間
// 把 5, 6, 7 co 過去，讓 bb 指向開頭，因此 aa, bb 的地址不同了，因為指向兩個不同的實體
fmt.Printf("bb -> %#v %p len:%d cap:%d\n", bb, bb, len(bb), cap(bb))
_ = append(aa, 11, 22, 33) // aa 的空間為 2 的 array 還是不夠放，因此要建另外個 array ，但是和 bb 完全沒關係
fmt.Printf("aa -> %#v %p len:%d cap:%d\n", aa, aa, len(aa), cap(aa))
fmt.Printf("bb -> %#v %p len:%d cap:%d\n", bb, bb, len(bb), cap(bb))

/*
aa -> []int{} 0xc000014110 len:0 cap:2
bb -> []int{5, 6, 7} 0xc00001a080 len:3 cap:4
aa -> []int{} 0xc000014110 len:0 cap:2
bb -> []int{5, 6, 7} 0xc00001a080 len:3 cap:4
*/

```

> 宣告和初始化，Nil slice != empty slice 喔

```go=
var a []int // nil 用在切始不知道有多少數量的 slice
a := make([]int, 0, N) // 當已知 range 的數量時可以用 make 預先配置 cap 較好效能
a := []int{1,2} // 當已知元素要塞時，透過初始塞入進行配置
```

```go=
var foo []string // nil slice
var bar = []string{} // empty slice
fmt.Println(foo == nil) // true
fmt.Println(bar == nil) // false
for _, v := range foo { // no errors
    fmt.Println("nothing, no print but no error", v)
}
for _, v := range bar { // no errors
    fmt.Println("nothing, no print but no error", v)
}

```

```go=
func remove(slice []int, idx int) []int {
    return append(slice[:idx], slice[idx+1:]...)
}

func main() {
    var s1 = []int{1,2,3,4,5}
    fmt.Println(remove(s1, 4))
}

```

### Map

> 像是 python dict 也就是 key,value 的 mapping，當 key 不存在的時候，會噴出 "" 空字串
> 
> 可以透過 delete 刪除 key
> 
> 本身 map variable 就是 pointer(reference 存地址) 因此不需要再使用 pointer 去指向該 instance 直接透過 n := m assign 就可以參考相同的結構
> 
> **它是無序的，所以每次執行的走訪結果也是不同的，要特別注意**

```go=
m := []map[string]map[string]string { // list of dict of dict
    {
        "a": {
            "a": "ok",
        },
        "b": {
            "b": "ok",
        },
    },
}
fmt.Println(m)

n := []map[string]string { // list of dict
    { "a": "a" },
    { "b": "a" },
    { "c": "a" },
}

fmt.Println(n)
n[0] = map[string]string {"d":"b"} // assign array[0] new dict
fmt.Println(n)

m[0]["a"]["c"] = "hello"
m = append(m, map[string]map[string]string {
    "ok": {
        "tt": "hello",
    },
})
fmt.Println(m)

delete(m[0]["a"], "a")
fmt.Println(m, len(m), len(m[0]["a"]))

result, ok := n[1]["c"]
fmt.Println(result, ok) // "", false
```

> 宣告和初始化，Nil map != empty map 喔

```go=
var m map[string]int // nil 用在指向某個同型別的 map，沒初始化不能操作使用
m := make(map[string]int) // 初始化為空map
m := map[string]int{"10":10} // 給值初始化
```

```go=
m := map[string]string{
    "abc": "124",
    "def": "456",
}
n := m
fmt.Printf("%+v %+v\n", m, n)
m["abc"] = "789"
fmt.Printf("%+v %+v\n", m, n)
n["hello"] = "world"
fmt.Printf("%+v %+v\n", m, n)

o := &m // no need to use pointer to reference map data
fmt.Printf("%+v %+v\n", m, *o)
(*o)["kkk"] = "100" // equal n := m, n["kkk"] = "100"
fmt.Printf("%+v %+v\n", m, *o)
```

### Channel

> 透過 channel 做 **等待** 用，或是 **event** 通知用
> 
> 因為 make(chan string) size = 0 為 unbuffered channel 所以行為像 **接力棒** 要給棒的，要等拿棒的，而要拿棒的要等給棒的，所以阻塞行為會發生在
> - 塞入時阻塞，等待到被拉取才能離開
> - 拉取時阻塞，等待到有被塞入才能離開
> 
> 當所有 goroutine 都無法滿足 channel 而執行完畢時，則發生 deadlock

```go=
const (
    DONE = "done"
)

// say 做 5 秒後發出 event DONE
func say(s string, e chan<- string) {
    for i := 1; i <= 5; i++ {
        fmt.Println(i, s)
        time.Sleep(1 * time.Second)
    }
    e <- DONE
}

func main() {
    event := make(chan string) // init block chan (size zero)
    go say("dachi", event)
    time.Sleep(3 * time.Second) // 睡 3 秒啟動下個 say
    go say("mike", event)

    fmt.Println("main do pull event first") // 3 秒後印出
    fmt.Println("main event pulled first", <-event) // 5 秒後 say("dachi") 發出 event 後，才能從阻塞中拿到 event
    fmt.Println("main do pull event second") // 5 秒後印出
    fmt.Println("main event pulled second", <-event) // 8 秒後 say("mike") 發出 event 後，才能從阻塞中拿到 event
}

// Result
// 1 dachi
// 2 dachi
// 3 dachi
// main do pull event first
// 1 mike
// 4 dachi
// 2 mike
// 5 dachi
// main event pulled first done
// main do pull event second
// 3 mike
// 4 mike
// 5 mike
// main event pulled second done

```

> 使用 channel 當 **變數** 做為多執行緒下共享變數，避免 **變數競爭** 狀態，設定

```go=
total := make(chan int, 1) 只有一格的 buffered channel
```

> 當 goroutine 讀取 channel 時，若 channel 有值，則可以取得，當取得後迫使其它 goroutine 也要拿 channel 的值時，因為無值必須等待，而當寫回去的瞬間，其它等待 channel 要拿取的 goroutine 可以啟動，但只有一個 goroutine 能拿到值，如此可以 **保證** channel 中的值，一次只能被一個 goroutine 存取 (拿出再寫回) 
> 
> 下面 case1, case2 行為完全不同，case1 是預料外的結果，而 case2 是正常的，因為 case1 當 goroutine 發出去時，瞬間 main goroutine 已經把 i 加到 11 了，所以當發出去的 goroutine 拿取 i 時是 11
> 
> case2 是透過 func paramter 來 **copy value** 來保證 goroutine 運算中用到的值，是 **啟動** goroutine 時 **傳入** 的計算值

```go=
func main() {
    var wg sync.WaitGroup
    total := make(chan int, 1)

    // case 1
    total <- 0

    for i := 1; i <= 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println("case1, i:", i)
            total <- <-total + i
        }()
    }
    wg.Wait()
    fmt.Println(<-total)

    // case 2
    total <- 0

    for i := 1; i <= 10; i++ {
        wg.Add(1)
        go func(num int) {
            defer wg.Done()
            fmt.Println("case2, i:", num)
            total <- <-total + num
        }(i)
    }

    wg.Wait()
    fmt.Println(<-total)
}
// Result
// case1, i: 11
// case1, i: 11
// case1, i: 11
// case1, i: 11
// case1, i: 10
// case1, i: 11
// case1, i: 11
// case1, i: 11
// case1, i: 8
// case1, i: 4
// 110
// case2, i: 1
// case2, i: 10
// case2, i: 2
// case2, i: 7
// case2, i: 5
// case2, i: 9
// case2, i: 8
// case2, i: 4
// case2, i: 6
// case2, i: 3
// 55
```

> - unbuffered channel 
>     - 等待用
>     - 接力棒
>     - 推/拉 相互等待
> - buffered channel
>     - 重點在 channel buffer 空或是滿
>         - 空時，拉資料會 block
>         - 滿時，推資料會 block
>         - 要自己控好 buffer
>     - 讀取 channel 時
>         - 重點在 channel close
>         - close 為讀取 channel 的終止條件
>         - for infinity or range 都可以以 close 為條件讀取全部

```go=
func main() {
    // unbuffered channel
    // main goroutine wait the other goroutine
    ch1 := make(chan string)
    go func() {
        fmt.Println("first goroutine running.")
        time.Sleep(3 * time.Second)
        ch1 <- "done"
    }()

    fmt.Println("main start to wait...")
    fmt.Println("main get channel string", <-ch1)

    // buffered channel
    ch2 := make(chan string, 10)
    for i := 0; i < 10; i++ {
        go func(num int) {
            fmt.Printf("%d go routine running.\n", num)
            time.Sleep(3 * time.Second) // every goroutine running 3s
            fmt.Printf("%d go routine done.\n", num)
            ch2 <- fmt.Sprintf("%d Done.", num)
        }(i)
    }
    // after 5s close the channel
    time.Sleep(5 * time.Second)
    close(ch2)

    for data := range ch2 {
        fmt.Println("Get channel result,", data)
    }
    _, ok := <-ch2
    if !ok {
        fmt.Println("channel is closed.")
    }

    // or for to watch ch2 data
    // for {
    //    data, ok := <-ch2
    //    if !ok {
    //        fmt.Println("channel is closed")
    //    }
    //    fmt.Println("result,", data)
    // }
    
    // Result
    // main start to wait...
    // first goroutine running.
    // main get channel string done
    // 1 go routine running.
    // 9 go routine running.
    // 6 go routine running.
    // 7 go routine running.
    // 3 go routine running.
    // 2 go routine running.
    // 4 go routine running.
    // 5 go routine running.
    // 0 go routine running.
    // 8 go routine running.
    // 8 go routine done.
    // 5 go routine done.
    // 1 go routine done.
    // 4 go routine done.
    // 9 go routine done.
    // 6 go routine done.
    // 2 go routine done.
    // 7 go routine done.
    // 3 go routine done.
    // 0 go routine done.
    // Get channel result, 8 Done.
    // Get channel result, 5 Done.
    // Get channel result, 1 Done.
    // Get channel result, 4 Done.
    // Get channel result, 9 Done.
    // Get channel result, 6 Done.
    // Get channel result, 2 Done.
    // Get channel result, 7 Done.
    // Get channel result, 0 Done.
    // Get channel result, 3 Done.
    // channel is closed.
}
```

> 透過 select 來處理 channel block 的狀態
> - 當 channel 無法讀取時，default 處理
> - 當 channel 可讀取時，處理 channel 的資料
> select 本身可以當成阻塞方法，防止 main routine 離開
> ```go=
> for {time.Sleep(time.Second)}
> ```
> 等同於
> ```go=
> select{}
> ```

```go=
func main() {
    ch := make(chan string)

    go func() {
        fmt.Println("Goroutine: start running...")
        // do some heavy computing job
        time.Sleep(10 * time.Second)
        fmt.Println("Goroutine: run done...")
        ch <- "done"
    }()

    // wait goroutine every second
    for {
        select {
        case result := <-ch:
            fmt.Println("Mainroutine: check goroutine is", result)
            goto END
        default:
            time.Sleep(1 * time.Second)
            fmt.Println("Mainroutine: goroutine not complete, wait for 1 second")
        }
    }
    END:
}
```
```go=
// 時限內，展開 10 個工人處理，並進行匯總結果
func run(ID int, result chan<- map[int]string, wg *sync.WaitGroup) {
    defer wg.Done()
    t := time.Now().UnixNano()
    rand := rand.New(rand.NewSource(t))
    sleepInSec := rand.Intn(10)
    fmt.Printf("%d goroutine will sleep in %d seconds, start ...\n", ID, sleepInSec)
    time.Sleep(time.Duration(sleepInSec) * time.Second)
    fmt.Printf("%d goroutine sleep %d done.\n", ID, sleepInSec)
    result <- map[int]string {
        ID: fmt.Sprintf("sleep %d secs", sleepInSec),
    }
}

func main() {
    var wg sync.WaitGroup
    resultChan := make(chan map[int]string)
    finishChan := make(chan string)
    timesUpChan := make(chan bool)

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go run(i, resultChan, &wg)
    }
    go func() {
        fmt.Println("start to watch all routine...")
        wg.Wait()
        finishChan <- "Done"
    }()
    go func() {
        fmt.Println("start the timer, and wait for 7 seconds.")
        <-time.After(time.Duration(7) * time.Second)
        timesUpChan <- true
    }()

    resultData := map[int]string{}
    Loop:
        for {
            select {
            case result := <-resultChan:
                for k, v := range result {
                    resultData[k] = v
                }
            case <-finishChan:
                fmt.Println("all routine done.")
                for i := 0; i < len(resultData); i++ {
                    fmt.Printf("routine %d %s\n", i, resultData[i])
                }
                break Loop
            case <-timesUpChan:
                fmt.Println("times up.")
                break Loop
            }
        }
}

```
```go=
// 測試拉取，防止卡住
func main() {
    messages := make(chan string)
    signals := make(chan bool)

    select { // 看 case 的當下立刻「選擇」處理
    case msg := <-messages: //沒東西等待拉取，跳過
        fmt.Println("received message", msg)
    default:
        fmt.Println("no message received")
    }

    msg := "hi"
    select {
    case messages <- msg: //沒人等著接收，跳過
        fmt.Println("sent message", msg)
    default:
        fmt.Println("no message sent")
    }
}
```

> 透過 select 可以測試 channel 並防止 blocking (塞的進去就塞，塞不進去就跳過。拉的出來就拉，拉不出來就跳過)

```go=
func main() {
    type Book struct{id int}
    bookshelf := make(chan Book, 3)

    for i := 0; i < cap(bookshelf) * 2; i++ {
        select {
        case bookshelf <- Book{id: i}:
            fmt.Println("succeeded to put book", i)
        default:
            fmt.Println("failed to put book")
        }
    }

    for i := 0; i < cap(bookshelf) * 2; i++ {
        select {
        case book := <-bookshelf:
            fmt.Println("succeeded to get book", book.id)
        default:
            fmt.Println("failed to get book")
        }
    }
}
/* result
succeeded to put book 0
succeeded to put book 1
succeeded to put book 2
failed to put book
failed to put book
failed to put book
succeeded to get book 0
succeeded to get book 1
succeeded to get book 2
failed to get book
failed to get book
failed to get book
*/
```
```go=
// 第一個完成
func source(c chan<- int32) {
    ra, rb := rand.Int31(), rand.Intn(3) + 1
    // Sleep 1s/2s/3s.
    time.Sleep(time.Duration(rb) * time.Second)
    c <- ra
}

func main() {
    rand.Seed(time.Now().UnixNano())

    startTime := time.Now()
    // c must be a buffered channel.
    c := make(chan int32, 5)
    for i := 0; i < cap(c); i++ {
        go source(c)
    }
    // Only the first response will be used.
    rnd := <-c
    fmt.Println(time.Since(startTime))
    fmt.Println(rnd)
}
```
```go=
// 同上，透過 select 來抓
func source() <-chan int32 {
    c := make(chan int32, 1) // 必須有一格，以防止塞不要進去
    go func() {
        ra, rb := rand.Int31(), rand.Intn(3)+1
        time.Sleep(time.Duration(rb) * time.Second)
        c <- ra
    }()
    return c
}

func main() {
    rand.Seed(time.Now().UnixNano())

    var rnd int32
    // 阻塞到任一個 source 可以拉到資料為止 (拿第一個完成的)
    select{
    case rnd = <-source():
    case rnd = <-source():
    case rnd = <-source():
    }
    fmt.Println(rnd)
}
```
```go=
// 限時內的請求
func requestWithTimeout(timeout time.Duration) (int, error) {
    c := make(chan int)
    go doRequest(c) // 可能會超出預期的時間

    select {
    case data := <-c:
        return data, nil
    case <-time.After(timeout):
        return 0, errors.New("timeout!!!")
    }
}
```
```go=
// 一定速率進行請求
type Request interface{}
func handle(r Request) {fmt.Println(r.(int))}

const RateLimitPeriod = time.Minute
const RateLimit = 200 // 一份鐘內處理 200 個請求

func handleRequests(requests <-chan Request) {
    // 先分配好請求的計時器要有多少個
    quotas := make(chan time.Time, RateLimit)

    // 算出速率啟動 ticker 讓每速率下啟動塞入 quotes
    go func() {
        tick := time.NewTicker(RateLimitPeriod / RateLimit)
        defer tick.Stop()
        for t := range tick.C { // 每 tick 一次就試著塞看看
            select {
            case quotas <- t:
            default: // 塞不了，就不做事等待下次 tick
            }
        }
    }()

    for r := range requests {
        <-quotas // 拿出已分配好的 slot 它已經是被 tick 分段好的 queue 了
        go handle(r)
    }
}

func main() {
    requests := make(chan Request)
    go handleRequests(requests)
    // time.Sleep(time.Minute)
    for i := 0; ; i++ {requests <- i}
}

```

> channel close 很重要，才能判斷結束執行
> ==只有 channel 關閉了，去拉才「永遠」不會卡住，但塞進一個 close channel 會 panic==
> ```go=
> job, ok := <-jobs // 當 channel closed, ok 會是 false
> // ok = true "channel 未關閉，值有效"
> // ok = false "channel 已關閉，值為 zero value"
> ```
> 關閉一個 nil channel 會 panic
> ```go=
> var c chan int
> close(c)
> ```
> 行為公式表
> | Operation | Nil Channel | Closed Channel | 一般 Channel |
> | -------- | -------- | -------- | -------- |
> | Close | panic | panic | 成功關閉 |
> | 塞值進去 | 卡住 (block) | panic | 卡住或成功塞入 |
> | 拉值出來 | 卡住 (block)| 永遠拿出 (0,false) 且不會卡住 (never block) | 卡住或成功讀取 |

```go=
// 1 producer, 1 comsumer, N jobs
func worker(jobs <-chan int, done chan<- string) {
    for {
        job, ok := <-jobs //等同於 job := range jobs
        if ok {
            fmt.Println("receive job", job)
        } else {
            fmt.Println("no available job")
            done <- "done"
            break
        }
    }
}

func main() {
    jobs := make(chan int, 10)
    event := make(chan string)

    go worker(jobs, event) // start comsumer
    time.Sleep(time.Duration(3) * time.Second)
    for i := 1; i <= 5; i++ {
        jobs <- i // produce job to channel
        time.Sleep(time.Duration(1) * time.Second)
        fmt.Println("set job", i, "to jobs")
    }
    close(jobs) // close 是終止條件，非常重要

    <-event // wait to all jobs done
}
/* result
receive job 1       <- worker (goroutine)
set job 1 to jobs   <- produce (mainroutine)
receive job 2
set job 2 to jobs
receive job 3
set job 3 to jobs
receive job 4
set job 4 to jobs
receive job 5
set job 5 to jobs
no available job
*/
```
```go=
// 1 producer, 3 comsumer, N jobs
const (
    NUM_JOBS = 10
    BOT = 3
)

func bot(id int, jobs <-chan int) {
    fmt.Println("bot", id, "wakeup.")
    for job := range jobs { // range 會 block until channel close
        // 就算 jobs 有多個資料，每次只會拿出一個
        // 而且保證競爭取得 channel 內容時，保證只有一個 routine 會拿到
        fmt.Println("bot", id, "started job", job)
        time.Sleep(time.Duration(3) * time.Second)
        fmt.Println("bot", id, "finished job", job)
    }
    fmt.Println("bot", id, "exit.")
}

func main() {
    jobs := make(chan int, NUM_JOBS) // 其實用 unbuffered 也行，只要一生產就馬上被消費
    for i := 1; i <= BOT; i++ {
        go bot(i, jobs)
    }
    // bot 3 wakeup.
    // bot 1 wakeup.
    // bot 2 wakeup.
    time.Sleep(3 * time.Second)

    jobs <- 1
    // bot 3 started job 1
    jobs <- 2
    // bot 1 started job 2

    time.Sleep(3 * time.Second)
    // bot 3 finished job 1
    // bot 1 finished job 2

    jobs <- 5
    // bot 2 started job 5
    jobs <- 6
    // bot 3 started job 6
    jobs <- 7
    // bot 1 started job 7

    time.Sleep(2 * time.Second)
    // bot 1 finished job 7

    jobs <- 9
    // bot 1 started job 9

    close(jobs) // 當 close chan 時，才可以讓 range 離開，producer 必須做到關閉 channel 責任
    // bot 2 finished job 5
    // bot 2 exit.
    // bot 3 finished job 6
    // bot 3 exit.
    // bot 1 finished job 9
    // bot 1 exit.

    <-time.After(30 * time.Second) // 時間到離開 main routine
}
```

> 養成好習慣，當 channel close 之後設為 nil

```go=
// 透過 select 讀取關閉的 channel 要設為 nil 讓它 block forever

func main() {
    a := make(chan int)
    b := make(chan int)
    go func() {
        time.Sleep(5 * time.Second)
        a <- 10
    }()
    go func() {
        time.Sleep(8 * time.Second)
        a <- 8
    }()
    go func() {
        time.Sleep(10 * time.Second)
        fmt.Println("close chan a")
        close(a) // 關閉 channel 會導致 <-a 的處理總是拿到 0, false
    }()
    for {
        select {
        case v, ok := <-a:
            if !ok {
                a = nil // 當發現 chan a 被關閉了，設為 nil 讓 chan a 永遠阻
                // 如果沒設為 nil 每次 select 都會進到這邊，就開始燒 CPU 了
            } else {
                fmt.Println("a", v)
                // 理論上關閉 chan 不能再有 chan 的讀取，需要以 !ok 為條件，作為終結
            }
        case v, ok := <-b:
            if !ok {
                b = nil
            } else {
                fmt.Println("b", v)
            }
        case <-time.After(3 * time.Second):
            fmt.Println("no data")
        }
    }
}
```
```go=
// 展開同時最多幾個 Job limit
doingJobQueue = make(chan struct{}, maxConcurrent)
go loop()

func loop() {
    for {
        doingJobQueue <- struct{}{} // 有空間可塞入，可同時處理最多 max job，塞不進去就 block 直到有 job 做完
        go func() {
            handleJob(msg)
            <-doingJobQueue // job done 從 queue 中排出
        }
    }
}
```
```go=
// stateful goroutines by channel
type writeOp struct {
    key int
    val int
    ok  chan bool
}

func main() {
    writes := make(chan writeOp)

    state := make(map[int]int)
    stop := false

    go func() {
        for {
            select {
            case write := <-writes: // 2. 拉取 writes queue 進行處理
                state[write.key] = write.val
                write.ok <- true // 3. 處理完成送個訊號
            }
        }
    }()

    for w := 0; w < 10; w++ {
        go func() { // write generator
            for {
                write := writeOp{
                    key: rand.Intn(5),
                    val: rand.Intn(100),
                    ok:  make(chan bool),
                }
                writes <- write // 1. 丟進 writes 等待被處理
                <-write.ok      // 4. 等待處理完成的訊號，再往下做

                if stop {
                    fmt.Println("receive stop")
                    return
                }
            }
        }()
    }

    <-time.After(time.Duration(5) * time.Second)
    stop = true
    <-time.After(time.Duration(3) * time.Second)
    fmt.Println("state:", state)
}
```
```go=
// Fan-out
func worker(in <-chan int, out chan<- int) {
    for data := range in {
        // Process data
        result := processData(data)
        out <- result
    }
}

// Fan-in
func merge(channels []<-chan int, out chan<- int) {
    var wg sync.WaitGroup
    for _, ch := range channels {
        wg.Add(1)
        go func(ch <-chan int) {
            defer wg.Done()
            for data := range ch {
                out <- data
            }
        }(ch)
    }
    wg.Wait()
    close(out)
}
```
```go=
// fan-out and fan-in
func main() {
    input := make(chan int)
    result := make(chan int)
    data := []int{}
    sig := make(chan struct{})

    go func() {
        for i := range result { // fan-in collect result to data
            data = append(data, i)
        }
        sig <- struct{}{} // 資料整理完成後送 sig
    }()

    wg := sync.WaitGroup{}
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go worker(i, input, &wg, result) // fan-out worker
    }

    for i := 0; i < 10; i++ { // produce data
        input <- i
    }
    close(input) // end data input

    wg.Wait()     // all workers are done
    close(result) // exit collect

    <-sig // 等待資料整理完成
    sum := 0
    for _, i := range data {
        sum += i
    }

    fmt.Println("done", sum)
}
```
```go=
// ping pong goroutine
type sig struct{}

func main() {
    i := 1
    a := make(chan sig)
    b := make(chan sig)
    wg := sync.WaitGroup{}

    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            <-a
            fmt.Println("a:", i)
            i++
            if i > 10 {
                select {
                case b <- sig{}: // if can write
                default: // skip
                }
                fmt.Println("a exit")
                return
            }
            b <- sig{}
        }
    }()

    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            <-b
            fmt.Println("b:", i)
            i++
            if i > 10 {
                select {
                case a <- sig{}: // if can write
                default: // skip
                }
                fmt.Println("b exist")
                return
            }
            a <- sig{}
        }
    }()

    a <- sig{}
    wg.Wait()

    fmt.Println("all done.")
}
```
```go=
// errgroup 把所有平行處理都丟進去，只抓致少一個 Error
// eg.Wait() 會等待結束，並回傳有任何 error 的狀況
func (y *YahooFinanceService) GetMixedQuote() (finance.MixedQuoteData, error) {
    eg := errgroup.Group{}

    forexQuotes := []finance.Quote{}
    indicatorQuotes := []finance.Quote{}

    eg.Go(func() error {
        q, err := y.GetForexs([]finance.ForexExchange{
            {
                FromUint: currency.USD,
                ToUnit:   currency.TWD,
            },
            {
                FromUint: currency.JPY,
                ToUnit:   currency.TWD,
            },
        })
        if err != nil {
            return fmt.Errorf("get forex data from finance service failed. %w", err)
        }
        forexQuotes = q
        return nil
    })
    eg.Go(func() error {
        q, err := y.GetIndicators(
            []string{"^GSPC", "^DJI", "^IXIC"},
        )
        if err != nil {
            return fmt.Errorf("get indicator data from finance service failed. %w", err)
        }
        indicatorQuotes = q
        return nil
    })

    err := eg.Wait()

    data := finance.MixedQuoteData{
        Indicators: indicatorQuotes,
        Forexs:     forexQuotes,
    }

    return data, err
}
```
```go=
// semaphore 固定展開一定數量平行處理
// 搭配 waitGroup 做所有的 routine 都完成的控制
func main() {
    data := []string{
        "ok1",
        "ok2",
        "ok3",
        "ok4",
        "ok5",
        "ok6",
        "ok7",
        "ok8",
        "ok9",
        "ok10",
    }

    wg := sync.WaitGroup{}
    sem := make(chan struct{}, 3)
    for _, d := range data {
        sem <- struct{}{} // acquire a slot
        wg.Add(1)
        go func() {
            defer func() {
                <-sem // release a slot
                wg.Done()
            }()
            time.Sleep(time.Second)
            fmt.Println("go routine say:", d)
        }()
    }
    wg.Wait()

    fmt.Println("all finished")
}
```
```go=
// 生產消費模型，消費者平行消費
// 透過 done 來確認完成消費都完成了
func producer(ch chan<- string, amount int) {
    for i := range amount {
        msg := fmt.Sprintf("%d, ok", i)
        ch <- msg
        fmt.Println("produce:", msg)
        time.Sleep(200 * time.Millisecond)
    }
    close(ch)
}

func consumer(ch <-chan string, done chan<- struct{}) {
    wg := sync.WaitGroup{}
    sem := make(chan struct{}, 3)
    for data := range ch {
        sem <- struct{}{} // acquire a solt
        wg.Add(1)
        go func() {
            defer func() {
                <-sem // release a solt
                wg.Done()
            }()
            time.Sleep(time.Second)
            fmt.Println("go routine say:", data)
        }()
    }
    wg.Wait()

    done<-struct{}{}
}

func main() {
    ch := make(chan string, 20)
    done := make(chan struct{})

    go producer(ch, 10)
    go consumer(ch, done)

    fmt.Println("start wait for process")
    <-done
    fmt.Println("all finished")
}
```
```go=
// 透過 timeout 強制關閉，對有 graceful shutdown 的服務來操作
package main

import (
    "fmt"
    "time"
)

func main() {
    timeout := time.After(2 * time.Second)
    ch := make(chan struct{})

    go func() {
        time.Sleep(1 * time.Second)
        ch <- struct{}{}
    }()

    select {
    case <-timeout:
        fmt.Println("force cancel")
    case <-ch:
        fmt.Println("graceful stop")
    }
    fmt.Println("done")
}
```
```go=
// 透過 timer 由 routine 來做 force cancel
package main

import (
    "fmt"
    "time"
)

func main() {
    timer := time.AfterFunc(2*time.Second, func() {
        fmt.Println("force cancel")
    })
    defer timer.Stop() // 如果 graceful stop 提前結束就停止 timer

    fmt.Println("graceful stop")
    serverGracefulStop() // 模擬 graceful stop ，當 2 秒後 force shutdown 這邊將會返回

    fmt.Println("done")
}

func serverGracefulStop() {
    time.Sleep(5 * time.Second)
}

```

#### **close(ch) vs ch <- struct{}{}**

> 發送資料 (serverClosed <- struct{}{})：需要有協程在接收該資料，否則發送操作會阻塞。這種方式適合==一對一==的通知。

> 關閉通道 (close(serverClosed))：所有正在等待接收該通道資料的協程都會被喚醒，接收到零值。這種方式適合==一對多==的通知。

#### close ch
> 透過 close channel 可以讓等待的 channel routine 立刻返回 ==exit==

```go=
package main

import (
   "fmt"
   "time"
)

func main() {
   ch := make(chan int)

   go func() {
      ch <- 100
      time.Sleep(1 * time.Second)
      fmt.Println("done")
      close(ch) // 必須有中止 channel 的條件
   }()

   // close 是離開 channel 的方法
   for v := range ch {
      // 印到 channel 沒資料
      fmt.Println(v)
   }
}
```

#### for if ok channel
> 透過 ==if c, ok := <-ch; ok {}== 可以判斷 channel 有沒有關閉，如果是 ==關閉== ok 會是 ==false== 告訴你該 channel 已經關閉了

```go=
package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    go func() {
        ch <- 100
        time.Sleep(1 * time.Second)
        fmt.Println("done")
        close(ch) // 必須有中止 channel 的條件
    }()

    for {
        if v, ok := <-ch; ok {
            fmt.Println(v)
        } else {
            // 當 channel 關閉時, ok 為 false
            break
        }
    }
}
```

### Byte & Rune

> utf-8 會用 3 Byte 來表示，而用一個 1 Rune 表示
> 
> []string 用 range 來 iter 會是 Rune (符號)表示，但是用 len 實際看空間是用 bytes 計算

```go=
str := "hello 三民主義，吾黨所宗101" // utf-8 3 bytes [a-z][0-1] 1 byte

b := []byte("hello 三民主義，吾黨所宗101")
r := []rune("hello 三民主義，吾黨所宗101")

fmt.Println(str, len(str)) // 36=6+27+3
fmt.Println(b, len(b)) // 36=6+27+3
fmt.Println(r, len(r)) // 符號，所以 18=6+9+3

charCount := 0
for _, c := range str { // range 會以符號來佚代，但用 Len 是以 bytes 計算
    fmt.Println(c)
    charCount++
}
fmt.Println("===========")
for _, data := range b {
    fmt.Println(data)
}
fmt.Println("===========")
for _, data := range r {
    fmt.Println(data)
}
fmt.Println("===========")
fmt.Println(charCount, len(str)) // rune 18  bytes 36
```
```go=
str := "三民主義,吾黨所宗"
// 要計算有幾個字符，要先轉 rune
rstr := []rune(str)
cnt := len(rstr)
fmt.Println(cnt) // 9
// 要記算有幾個 bytes 要先轉 byte
bstr := []byte(str)
cnt = len(bstr) 
fmt.Println(cnt) // 8*3+1 = 25
```
```go=
// compare rune
func main() {
    f, _ := os.Open("./../day3.txt")
    defer f.Close()

    scanner := bufio.NewScanner(f)
    totalPriority := 0
    for scanner.Scan() {
        line := scanner.Text()
        mid := len(line) / 2 // ex: 22 / 2 = 11 (11個)
        part1 := line[:mid]
        part2 := line[mid:]

        item := findTheItem(part1, part2)
        priority := getItemPriority(item)
        fmt.Println(line, part1, part2, string(item), item, priority)
        totalPriority += priority
    }
    fmt.Println("totalPriority:", totalPriority)

}

func getItemPriority(item rune) int {
    if item > 97 {
        // lowercase
        return int(item) - 97 + 1
    } else {
        // uppercase
        return int(item) - 65 + 27
    }
}

func findTheItem(left string, right string) rune {
    for _, i := range left {
        for _, j := range right {
            if i == j {
                return i
            }
        }
    }
    return rune(0) // zero rune, zero value = int32
}
```

### Struct

> 就像 Array 一樣，一個 Struct 的變數存的不是 pointer 而是實際的結構
> 
> 所以透過 a := b 的指定是有 copy 的，而不是 copy address
> 
> 可以透過 pointer.field or (*pointer).field 來存取，golang 會自動 dereferecing pointer

```go=
var p Person //不是 nil 已經被初始化了，配置了預設值
```
```go=
type T struct {
    n int
    f float64
    next *T
}
fmt.Println([2]T{}) // [{0 0 <nil>} {0 0 <nil>}]
```
```go=
type Person struct {
	Name string
	Age int
}

func updatePerson(p *Person, newName string) *Person {
	p.Name = newName // equal to  (*p).Name = newName
	return p
}

func main() {
	p := Person{ // like array
		Name: "Dachi",
		Age: 30,
	}
	fmt.Printf("%+v\n", p)

	p.Age = 10
	fmt.Printf("%+v\n", p)

	s := &p // s is pointer to struct
	(*s).Age = 11 // use pointer to update struct data
	fmt.Printf("%+v\n", p)
	s.Age = 12 //  the pointers are automatically dereferenced.
	fmt.Printf("%+v\n", p)

	p2 := p // data copy not pointer copy
	fmt.Printf("%+v %+v\n", p, p2)
	p.Name = "Max" // change p will not change p2
	fmt.Printf("%+v %+v\n", p, p2)

	var p3 *Person
	p3 = &p // p3 point to p and p3 save it address of p
	fmt.Printf("%+v %+v\n", p, *p3)
	p.Name = "John" // change p and p3 will be changed, beacuse reference to the save instance
	fmt.Printf("%+v %+v\n", p, *p3)

	fmt.Printf("%+v %+v %d %d\n", p, *p3, p3.Age, (*p3).Age) // in struct pointer will be dereferencing automatically
	updatePerson(&p, "Ted")
	fmt.Printf("%+v %+v %+v\n", p, p2, *p3) // p2 is copied, so will not be changed by p or *p3 update data
}
```

```go=
// embedded struct
import (
    "encoding/json"
    "fmt"

    "github.com/golang-jwt/jwt"
)

type Claims struct {
    Account string `json:"account"`
    jwt.StandardClaims
}

func main() {
    claims := Claims{
        Account: "dachi",
        StandardClaims: jwt.StandardClaims{ // init for embedded struct
            Subject: "helloworld",
            Issuer:  "project A",
        },
    }
    result, _ := json.Marshal(claims)
    fmt.Println(string(result))
}
```

### Struct & Receiver

> - Value or Pointer
>      - value
>          - go 會使用 copy struct 方式傳入，因此拿到的是 struct 的複本
>          - 用在讀取時，而不會對 struct 有寫入動作時
>      - pointer
>          - go 會使用 copy struct address 方式傳入，因此拿到的是 struct 的地址，而透過 struct 的成員存取 `. (dot)` 會自動 dereferencing 到要修改的結構，不需要 `(*s).field`
>          - 可用在讀寫
>          - 如果成員非常多的話，用 Pointer 傳入，可以剩下 Copy Value 更多的資源
> - 如果成員變數是 slice, map, channel, func, interface, pointer 的話，本身就是 address 了，所以透過 value 的傳遞進行修改的話，還是會改到本身變數去喔
> - 對於 slice 要特別小心，因為如果需要改變 len(), cap() 大小時，如 append 需要注意是否指向正確的 slice
> - 就是說如果 receiver 是 value copy 而該 value 是指向真正資料的 address 時，直接對最終的資料做修改，完全無關 receiver 是 by value or by pointer 一樣都改的到最終的資料
> - 這邊的 value, pointer 指的是對 struct **本身** 的傳遞

```go=
type Cart struct {
    Name  string // value type
    Price int // value type
    items []string // reference type (address)
}

func (c Cart) GetPrice() {
    fmt.Println("price:", c.Price)
}

func (c Cart) UpdatePrice(price int) {
    // c 為 struct copy 因此 c.Price 不是本來 struct 而是 copied 改變它不會影響本來的 struct
    c.Price = price
}

func (c Cart) InsertItem(itemName string) {
    // c 為 struct copy，但 items 是 reference type (address) 因此對它做操作仍然會改變該 items 的內容
    fmt.Printf("%p\n", c.items) // 可以看到 address 是和建立的 items slice 完全一樣

    for k, v := range(c.items) { // 找第一個不是空的空位塞入
        if v == "" {
            c.items[k] = itemName // 直接改到目標 slice 受體的內容
            break
        }
    }

    c.items = append(c.items, itemName) // 這個是無效的，因為 c 是 copied
    // 所以把 append 完新的 slice address 塞進去，只是丟給 copied struct 不會影響本來的 c struct
}

func (c Cart) ShowCart() {
    fmt.Println(c)
}

func main() {
    items := make([]string, 10)
    items[0] = "apple"
    fmt.Printf("%p\n", items)
    c := &Cart{
        "bage",
        100,
        items, // 把 slice address 塞過去
    }
    c.ShowCart()
    c.UpdatePrice(200)
    c.InsertItem("cake")
    c.ShowCart()
}

// result
// 0xc000072000
// {bage 100 [apple         ]}
// 0xc000072000
// {bage 100 [apple cake        ]}
```

### Struct & Interface

> 1. 先定義 struct 可以考慮字首大小，或是field字首大小讓 package 外可以 ref 到
> 2. 再定義要開出來的 Interface 的功能
> 3. 實作建構 struct 初始的建構函數，回傳 Interface (這邊的 good practice 參考[ref](https://bryanftan.medium.com/accept-interfaces-return-structs-in-go-d4cab29a301b))
> 4. 實作符合 Interface 要求的功能

```go=
// Point

type IPoint interface { // public
    X() int
    Y() int
    SetX(int)
    SetY(int)
    Show()
}

type point struct { // private, invisable from the other package
    x int
    y int
}

func NewPoint(x int, y int) IPoint { // construct, return interface (interface is a pointer)
    p := &point{
        x: x,
        y: y,
    }
    return p
}

func (p *point) Show() { // implement debug
    fmt.Println("X:", p.x, "Y:", p.y)
}

func (p *point) X() int { // implement get property
    return p.x
}

func (p *point) Y() int { // implement get property
    return p.y
}

func (p *point) SetX(x int) { // implement set property
    p.x = x
}

func (p *point) SetY(y int) { // implement set property
    p.y = y
}

// Point3D

type IPoint3D interface {
    IPoint // extends interface
    Z() int
    SetZ(int)
}

type Point3D struct { // public, visable from the other package
    point// extends from the point struct
    z int
}

func NewPoint3D(x int, y int, z int) *Point3D { // return struct pointer
    p := &Point3D{}
    p.SetX(x)
    p.SetY(y)
    p.SetZ(z)
    // or
    /*
    p := &Point3D{
        point: point{
            x: x,
            y: y,
        },
        z: z,
    }
    */
    // or
    /*
    p := &Point3D{}
    p.x = x
    p.y = y
    p.z = z
    */
    return p
}

func (p *Point3D) Z() int {
    return p.z
}

func (p *Point3D) SetZ(z int) {
    p.z = z
}

func (p *Point3D) Show() { // override show method
    fmt.Println("X:", p.point.X(), "Y:", p.point.Y(), "Z:", p.Z())
}

func main() {
    point := NewPoint(2, 3)
    point.SetY(5)
    point.Show()
    fmt.Println(point.X())

    point3D := NewPoint3D(1, 2, 3)
    point3D.z = 100
    fmt.Println(point3D.X(), point3D.Y(), point3D.Z())
    point3D.Show()
}

```

### Interface Assertion

> 複雜的 interface variable 要一層一層 assertion 才能 range

```go=
package main

import "fmt"

func main() {
	payload := map[string]interface{}{
		"keys": []interface{}{
			"hello",
			"world",
		},
		"data": map[string]interface{}{
			"foo": []struct {
				source string
				price  int
			}{
				{
					source: "chichen",
					price:  100,
				},
				{
					source: "fish",
					price:  150,
				},
			},
		},
	}

	keys := (payload["keys"]).([]interface{})
	for k, v := range keys {
		fmt.Println(k, v.(string))
	}

	data := (payload["data"]).(map[string]interface{})
	for k, v := range data {
		newV := v.([]struct {
			source string
			price  int
		})
		for _, element := range newV {
			fmt.Println(k, element.source, element.price)
		}
	}
}
```

> interface 什麼都能裝，但是要經過 assertion 才能轉為明確的型別，才可以用它的 method 不然只能用 interface 定出來的 method sets

```go=
// member, member2 都實作了 show()
// 只有 member 有 debug()
// mSlice 可以放入所有實作 show() 的 obj
// 但只有 assetion 回原本的 member 才能使用 debug() method

type member struct {
    name string
    email string
    age int
}

func (m member) show() {
    fmt.Println("show:", m.name)
}

func (m member) debug() {
    fmt.Println("debug:", m)
}

type member2 string

func (m member2) show() {
    fmt.Println("show:", m)
}

func main() {
    var mSlice []interface{
        show() // 符合 interface 要求的 method sets
    }
    mSlice = append(mSlice, member{
        name: "john",
        email: "john@sshellab.com",
        age: 35,
    })
    mSlice = append(mSlice, &member{
        name: "ted",
        email: "ted@sshellab.com",
        age: 25,
    })
    mSlice = append(mSlice, member2("dachi"))
    mm := member2("geoge")
    mSlice = append(mSlice, &mm)

    // use .(type) to switch case the type
    for _, v := range mSlice {
        v.show() // 使用 interface 中定義的 show method
        switch v.(type) {
        case member:
            fmt.Println("is member:", v)
            member := v.(member)
            member.debug() // 經過 assertion 轉為 member 可使用 debug 的 method
        case *member:
            fmt.Println("it *member:", v)
            member := v.(*member) // 本來放進的就是指票，進行斷言也要使用指標型別
            member.debug()
        case member2:
            fmt.Println("is member2:", v)
        case *member2:
            fmt.Println("is *member2:", v)
        }
    }
    // or use val, ok return 2 value to check assertion success
    if val, ok := mSlice[1].(*member); ok {
        // assertion success
        fmt.Printf("%v\n", val)
    } else {
        // assertion failed
        fmt.Println("is not type *member")
    }
}
```

### Func

> Variadic Function 的不定參數，只能用在最後的 parameter ex: func foo(s string, num ...int)

```go=
func add(a int, b int) (int, int) {
    d := a + b
    return d, a
}

func sum(s string, nums ...int) { // Variadic Functions
    fmt.Println(nums, s)
    total := 0
    for _, v := range nums {
        total += v
        fmt.Printf("v:%d total:%d\n", v, total)
    }
}

func main() {
    m, n := add(1, 2)
    fmt.Printf("helloworld %d %d\n", m, n)

    main_nums := []int{1, 2, 3}
    sum("run1", main_nums[0])
    sum("run2", main_nums[0], main_nums[2])
    sum("run3", main_nums...) // pass slice to variadic functions and umpack with ...

    interface_slice := []interface{}{ // interface type slice to hold everying type
        "test1",
        100,
    }
    fmt.Println(interface_slice) // unpack interface type slice
}
```

### Closure
```go=
func intSeq() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

func ain() {
    callback_func := intSeq()
    fmt.Printf("%d\n", callback_func())
}
```
```go=
// factory pattern 
// 透過不同的 TransferType 丟回一致的建構 function
// 且呼叫之後得到 instance 都滿足了 interface Carrier
func (fg *factoryGeneratorImpl) Gen(tp TransferType) Factory {
    switch tp {
    case AzureToAws:
        return func(srcKey, srcSecret, dstKey, dstSecret string) Carrier {
            return &azureAwsImpl{
                srcAccount: srcKey,
                srcKey:     srcSecret,
                dstAccount: dstKey,
                dstKey:     dstSecret,
            }
        }
    case AzureToAzure:
        return func(srcKey, srcSecret, dstKey, dstSecret string) Carrier {
            return &azureImpl{
                srcAccount: srcKey,
                srcKey:     srcSecret,
                dstAccount: dstKey,
                dstKey:     dstSecret,
            }
        }
    case AwsToAzure:
        return func(srcKey, srcSecret, dstKey, dstSecret string) Carrier {
            return &awsAzureImpl{
                srcAccount: srcKey,
                srcKey:     srcSecret,
                dstAccount: dstKey,
                dstKey:     dstSecret,
            }
        }
    default:
        return nil
    }
}

```

### Iterates (Range)
```go=
nums := []int{
    2,
    3,
    4,
}

for i, num := range nums { // for slice, array return index, element
    fmt.Println(i, num)
    fmt.Printf("%T, %T\n", i, num)
}

for _, num := range nums { // ignore the first return index
    fmt.Println(num)
}

kvs := map[string]string{
    "a": "apple",
    "b": "banana",
}
for k, v := range kvs { // for map return key, value
    fmt.Printf("%s -> %s\n", k, v)
}
for _, v := range kvs { // only use the value
    fmt.Println(v)
}

s := "hello world"
for i, char := range s { // for string return index, charactor ASCII
    fmt.Printf("%T, %T, %s\n", i, char, string(char))
}

student_rank_list_map := []map[string]int{
    {
        "ken": 1,
        "ted": 2,
    },
}
student_rank_list_map = append(student_rank_list_map, map[string]int{
    "john": 3,
})
for i, rank_map := range student_rank_list_map {
    for k, v := range rank_map {
        fmt.Printf("%d, %s, %d\n", i, k, v)
    }
}
```

### Error

```go=
type error interface {
    Error() string
}
```
> 其實 error 是個 interface 只要滿足 method sets 實作了 Error() string 都可以是 error
> 
> 當 error 用在 fmt 將有以下特性
> 
> >If an operand implements the error interface, the Error method will be invoked to convert the object to a string
> 
> error 是個 interface 其中包含了 **(Type, Value)** 兩者都是 nil 才會是真的 nil 
> ```go=
> func do() error {
>     var err *someError
>     return err // nil of type *someError
> }
> 
> if err := do(); err == nil {
>     // always not equal to nil (*someError, nil)
> }
> ```
> 所以為了讓外面可以 **if err != nil {}** 那就要讓拋回 error interface 的 func 總是以 nil 傳回，不要 declare 是那種型別的 error，不然外面會有雷

```go=
func runReturnErr() error {
    return fmt.Errorf("create an error obj")
}

type myErr struct {
    msg string
}

func (e myErr) Error() string {
    return fmt.Sprintf("my error: %s", e.msg)
}

func (e myErr) Detail() {
    fmt.Printf("detail of error: %s\n", e.msg)
}

func NewMyErr(errStr string) error {
    return myErr{
        msg: errStr,
    }
}

func main() {
    // type error interface { Error() string }
    err := runReturnErr()
    if err != nil {
        // 以下全部一樣
        fmt.Printf("%s\n", err.Error()) // 呼叫 .Error() 可取得 err 內容
        fmt.Printf("%s\n", err) // 呼叫 .Error() 可取得 err 內容
        fmt.Println(err) // 直接印 err 可以呼叫 String()
    }

    // use custom error
    customErr := NewMyErr("you get an error!!!")
    fmt.Println(customErr)
    // assertion to myErr to use Detail() method
    if assertedMyErr, ok := customErr.(myErr); ok {
        assertedMyErr.Detail() // 轉型完使用 myErr 專有的 Detail()
    }

    var theErr = myErr{
        msg: "you get an error!!!",
    }
    if errors.Is(customErr, theErr) { // compare two error is the same
        fmt.Println("yes is myErr")
    } else {
        fmt.Println("no is not myErr")
    }
}
```
```go=
// define a project's error
package error

type DaoErrorType int

const (
    DB_ACCESS_FAILED DaoErrorType = iota
    RESOURCE_DUPLICATE
)

type DaoError struct {
    detail  string
    errType DaoErrorType
}

func NewDaoError(errType DaoErrorType, detail string) error {
    return DaoError{
        detail:  detail,
        errType: errType,
    }
}

func (e DaoError) Error() string {
    return e.detail
}

func (e DaoError) Type() DaoErrorType {
    return e.errType
}
/* use DaoError
daoErr, _ := err.(evergreen_error.DaoError)
switch daoErr.Type() {
    case evergreen_error.DB_ACCESS_FAILED:
        RFC7807ErrorResponse(ctx, http.StatusInternalServerError, "create asset video failed", daoErr, "")
    case evergreen_error.RESOURCE_DUPLICATE:
        RFC7807ErrorResponse(ctx, http.StatusBadRequest, "create asset video failed", daoErr, "")
}
*/
```

### Defer

> - 先宣告後執行
> - func call 最深層先執行到最淺層，且在離開 func 前執行
> - 當出現 defer 時需要執行的 func 中的值已經先 evaluate 完

```go=
func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

func main() {
    a := 1
    b := 2
    // 先宣告後執行
    defer calc("1", a, calc("10", 2, b)) // 10, 2, 2, return 4 先 evaluate 與 a = 1 先帶入了 ，再掛上 defer 最後執行

    a = 0
    defer calc("2", a, calc("20", a, b)) // 20, 0, 2, return 2 先 evaluate 與 a = 0 帶入，再掛上 defer 等後先執行

    b = 1

    // 執行 calc("2", 0, 2) => 2 result: 2, 0, 2, 2
    // 執行 calc("1", 1, 4) => 5 result: 1, 1, 4, 5
}
/*
10 2 2 4
20 0 2 2
2 0 2 2
1 1 4 5
*/
```
### Panic

> 遇到運作時無法處理的狀況，就是 panic 直接結束離開，一般用在條件不符合程式運作的基本需求下

```go=
import (
    "os"
)

func main() {
    _, err := os.Stat("/test") // 檔案必須存在，否則不能運作
    if err != nil {
        panic("the file should exist")
    }
}
```

## OOP

> 透過 struct embedded 和 interface 來實現

```go=
package main

import "fmt"

type Car struct {
    name string
}

func (c *Car) Start() {
    fmt.Println(c.name, "engine start")
}

func (c *Car) Stop() {
    fmt.Println(c.name, "engine stop")
}

type VW struct {
    Car
}

func NewVW(name string) *VW {
    return &VW{
        Car: Car{
            name: name,
        },
    }
}

func (v *VW) Start() {
    fmt.Println("VW", v.name, "engine start")
}

type Toyata struct {
    Car
}

func NewToyata(name string) *Toyata {
    return &Toyata{
        Car: Car{
            name: name,
        },
    }
}

func (t *Toyata) Stop() {
    fmt.Println("toyata", t.name, "engine stop")
}

type CarRun interface {
    Start()
    Stop()
}

func show(c CarRun) {
    c.Start()
    c.Stop()
}

func main() {
    car1 := NewVW("sportsvan")
    show(car1)
    car2 := NewToyata("camery")
    show(car2)
}
```

## Go Multiple version (before go1.21)

[Managing Go installations](https://go.dev/doc/manage-install) (deprecated)
> Go 可以讓你在 local 裝多個版本，但必須是先從標準 download page 先裝一個 **Global** 版本，再由這個版本的 go 來安裝其它的版本

- go1.21.4 (/usr/local/go) **Go 官方安裝標準目錄**
    - go1.22.4 (~/sdk/go1.22.4) **由 go1.21.4 來安裝的 1.22.4**
    - go1.22.0 (~/sdk/go1.22.0) **由 go1.21.4 來安裝的 1.22.0**
    - ...

```bash=
# install go 1.22.4
go install golang.org/dl/go1.22.4@latest
go1.22.4 download
go1.22.4 env GOROOT
```
```bash=
# uninstall go 1.22.4
rm -rf ~/go/bin/go1.22.4
rm -rf ~/sdk/go1.22.4
```

> Go 1.21 之後**將 go 版本和 go toolchain 版本作為一個 module 的依賴來管理**，這表示當在 go.mod 中定義了 go version 則 local go version 只要在 1.21 以後的版本，都可以自動依 go.mod version 進行 go version, toolchain 的下載
> 而在 go.mod version 中的版號變成**最小滿足條件**

- 情境1
    - local toolchain: 1.21.4
    - go.mod version 1.22.4

這種情況 go toolchain 本身設定 auto 會自動選擇，因此會自動安裝 toolchain 1.22.4 (~/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.darwin-arm64/) 來滿足編譯

但是如果透過修改 **GOTOOLCHAIN=local** 表示使用 local (/usr/local/go) 安裝的 1.21.4 來編譯就會出現
```
go: go.mod requires go >= 1.22.4 (running go 1.21.4; GOTOOLCHAIN=local)
```

- 情境2
    - local toolchain: 1.21.4
    - go.mod version 1.22.4
    - GOTOOLCHAIN=go1.22.2
```
go: go.mod requires go >= 1.22.4 (running go 1.22.2; GOTOOLCHAIN=go1.22.2)
```

### Go project version control

> 從 1.21 開始前後兼容做了很大的改變
 go.mod 中的 go version 為**編譯語言最小可接受版本**，而 toolchain 版本為實際編譯可用的工具版本，因為保證向下相容的關係，go toolchain 會盡可能選擇最新的 toolchain，如果是設定 **GOTOOLCHAIN=auto** 表示它用了 local+auto 如果要求的 toolchain 比 local 大，就會自動安裝，否則使用 local (保證向下相容)

> Go 現況來說從 1.21 之後自動處理 go version 和 toolchain 的相依問題，因此只需要 go mod init 之後修改 go.mod version 它就會自動下載 toolchain，所以在 go project 目錄下，所有的 go version 都會跟著 module 中定義的 version 進行切換

[go1.21 toolchain](https://tonybai.com/2023/09/10/understand-go-forward-compatibility-and-toolchain-rule/)

```
go get go@1.22.4
go get toolchain@go1.22.4
```

- Project upgrade
    - go get go@1.22.4 (自動下載 toolchain 並使用新的 toolchain)
- Project downgrade
    - go get go@1.22.3
    - go get toolchain@go1.22.3 (手動指定，同步 go version)

![image](https://hackmd.io/_uploads/Bkg5AQYSR.png)

### Check Go module version

> go version -m gopls (不知道該 binary 是那個 go version build 出來時，可以這樣確認)

![image](https://hackmd.io/_uploads/SJXUDOZJkl.png)

## Go Module

>> go env | grep GO111MODULE
> 
> 存在一個神奇的環境變數，它在 1.11 開始出現 1.12 和 1.11 行為一樣，但是 1.13 之後又改變了，而 1.16 預設它是開啟
> 
> - ver 1.11, 1.12
>     - GO111MODULE=auto (就是看有沒有在 GOPATH 內)
>         - 在 GOPATH 外自動設為 GO111MODULE=on
>         - 在 GOPATH 內，就算有 go.mod 還是設為 GO111MODULE=off
>     - 所以在 GOPATH 內，想用 Go Module 就要
>         - GO111MODULE=on go get github.com/xx/xxx@v1.3.1
> - ver 1.13+
>     - GO111MODULE=auto (看 GOPATH 外內且有沒有 go.mod)
>         - 在 GOPATH 外且有 go.mod 就是 GO111MODULE=on
>         - 在 GOPATH 內且沒有 go.mod 就是 GO111MODULE=off
> - ver 1.16
>     - 預設就是使用 Go module
>     - 可以自己設為 GO111MODULE=auto (就看有沒有 go.mod 有就是 on)

> import package 時 Go 會先在 $GOROOT/src 下找，找不到，就會到 go.mod 定義中的位址去找 (GO111MODULE=on) 這樣就解除了 $GOPATH 的相依
> 
>> go env -w GOPRIVATE="gitlab.kkinternal.com/*"
>> 可以告訴 Go 跳過 proxy 直接抓 private repo
>> go env -u GOENV 當設錯要回復到 default 可以用 -u
>> GOPRIVATE go 和 **go get**, **go mod tidy** 有關，這個時預設會去 goproxy 拿，所以沒指定 private repo path 的時候，就會找不到，這個和 .netrc 沒關，而 netrc 是訪問的權限。如果 go.mod go.sum 都 commit 的話 (已過 go mod tidy) 那其實不需要 GOPRIVATE 這個 env 了，因為 go mod download 會對 go.sum 照單全收，所以只剩下訪問權限要處理而己

> 因為 Go ENV 是全域的，就是當改變了會影響所有 go 的環境，所以只過只是在該 console 要測試的話，直接使用 export GOENV=XXX 來暫時覆蓋就好，就算設錯了，直接關掉重開就好了，確定之後在寫入 Go ENV 中

> - [Using go Modules](https://blog.golang.org/using-go-modules)
> - go get 下載並加入一筆 go.mod 的 require 相依
>     - go get gitlab.com/dachichang/gotestmod@latest (最新的 tag)
>     - go get gitlab.com/dachichang/gotestmod@main (main branch 最新 commit)
>     - go get gitlab.com/dachichang/gotestmod@v1.0.0 (指定某個版本)
>     - go get gitlab.com/dachichang/gotestmod/v2 (抓第 v3)
> - go mod tidy 修改 go.mod 後，修正相依，以及刪除用不到的依賴
>     - 當改 require v1.0.1 -> v1.1.0 時，做 tidy 會自動下載相依，移除之前的相依
>     - 等同於做了 go mod download
> - go get -u ./... && go mod tidy
>     - 升級所有相依套件，並整理相依
> - go mod init
>     - 開一個 project 先做 go mod init ${PROJECT_NAME} 就可以視為整個目錄都是使用 Go Module
>     - 也等於 GO111MODULE=on 被設置了
> - go list -u -m all
>     - 列出所有使用的 modules
> - go list -m -versions gitlab.com/dachichang/gotestmod
>     - 列出該 module 所有的版本
> - go mod why all
>     - 列出所有 packages 為什麼被相依
> - go mod why -m all
>     - 列出所有 modules 為什麼為相依
> - go.mod
>     - module 定義模組位址和模組名稱 {MODULE_ADDR}/{MODULE_NAME}
>     - require 依賴的 module 的版本
>     - exclude 排除某個 module 的版本
>     - replace 使用不同的 module 替換本來 require 的版本
>         - 通常用 replace 來換掉本地開發時的相依很方便
>             - ex: replace gitlab.com/dachichang/gotestmod => ../gotestmod
>     - indirect 被間接依賴的 module 由 go get 或 go tidy 自動帶入

```bash=
go get gitlab.com/dachichang/gotestmod // 安裝最新版本(tags)
go get gitlab.com/dachichang/gotestmod@main // 安裝 main branch
go get gitlab.com/dachichang/gotestmod@v1.1.4 // 指定版本安裝
go list -m -versions gitlab.com/dachichang/gotestmod // 列出所有的版本
go get -u gitlab.com/dachichang/gotestmod // 升級到最新的版本，但是不會跳 major version
go get gitlab.com/dachichang/gotestmod@v1.1.3 // 退到指定版本
```
另外一種方式直接 vim go.mod 直接改裡面的版本再透過 go mod tidy 修正相依
```bash=
// go.mod

module a.com/gotestmaingg

go 1.16

require gitlab.com/dachichang/gotestmod v1.1.4 (改成 v1.1.3)

replace gitlab.com/dachichang/gotestmod => ../gotestmod
```
```bash=
go mod tidy // 修正相依，順便可以把之前 go get 的歷史記錄舊參考清掉
```
### 如何使用寫好的 module
```go=
// create main module
mkdir gotestmain
cd gotestmain
go mod init gotestmain

go get gitlab.com/dachichang/gotestmod // 下載相依 module

.
├── go.mod // 自動被建立，做完 go get 後會被新增一筆 require
├── go.sum // 記錄了 module 相依的歷史
├── lib.go // 部份的 main package 的公用函式
└── main.go // 主程式進入點，必須為 main package & func main()
```
```go=
// lib.go
package main

func echo(s string) string {
    return s
}
```
```go=
// main.go
package main

import (
    "fmt"

    "gitlab.com/dachichang/gotestmod/foo"      // import 指定 package 的 module 位址
    "gitlab.com/dachichang/gotestmod/foo/bar"  // 對像是 package 所以想有那個 package 都要 import 它的絕對位址
    "gitlab.com/dachichang/gotestmod/space"    // ${MODULE_PATH}/${MODULE_NAME}/${PACKAGE_DIR}
)

func main() {
    fmt.Println(echo("hello, world")) // echo 在 lib.go 同 main package 直接使用
    myfoo.ShowFoo() // 在 foo 目錄下的 myfoo package, 可以不同名，記得以 package name 為主不是目錄名稱，目錄名稱只是 import 時的位址
    fmt.Println(bar.ReturnShowBar()) // bar package 剛好和目錄名稱一樣
    point := space.NewPoint(1, 2)
    point.SetX(5)
    fmt.Println(point)
    bar.ShowPrivateBar()
}
```

> go get 和 go mod download/tidy 行為不同
> 
> - go get 看 remote 版本進行安裝，或是升級，因此需要 `git ls-remote -q origin` 來看 repo 的 go.mod 的資訊
>     - 套件升級直接透過 go get -u all
>     - 但遇到 private repo 時， **必須** 使用 `~/.netrc` 來解，因為它需要遞迴的進 subgroup 去看 go.mod
>     ```bash=
>     machine privategitlab.company.com login USERNAME_HERE password TOKEN_HERE
>     ```
>     - 就可以開心的 `go get -u all`
> 
> - go mod download or tidy 由已知的 go.mod 進行下載或是移除不需要的相依性
>     - 套件升級自己編輯 go.mod vX.X.X 並透過 tidy 進行下載和整理
>     - 無法對所有相依的 go module 進行 patch upgrade
>     - 如果下載了已經帶有 go.mod 且用到 private repo 而要下載相依的 go module，要透過以下的方法
>     ```bash=
>     git config \
>       --global \
>       url."https://USER:USER_TOKEN@privategitlab.com".insteadOf \
>       "https://privategitlab.com"
>     ```
>     - 就可以開心的 `go mod download`
> - 參考
>     - [Common-Mistakes](https://gist.github.com/MicahParks/1ba2b19c39d1e5fccc3e892837b10e21#common-mistakes)

### 使用 local module

> 當 main module 相依到自己推的其它 module 需要測試時，為確保 libary 的改動和 main module 的改動都正確，可以使用 **replace** 的定義把在 go.mod 中的 require 定義成 local module reference

```bash=
目錄結構如下
.
├── math (module shellab.io/math) <- library
│   ├── go.mod
│   └── math.go
└── school (module school) <- main
    ├── go.mod
    └── main.go
```
school/go.md
```go=
module school

go 1.18

require shellab.io/math v1.0.0

replace shellab.io/math => ../math
```
math/go.md
```go=
module shellab.io/math

go 1.18
```
school/main.go
```go=
package main

import (
        "fmt"

        "shellab.io/math" // 這將會使用到 local module 因為在 go.mod 使用 replace 取代為 local module
)

func main() {
        fmt.Println(math.Add(2,4))
}
```
### Major version of library

> - go module path 定義 version
>     - 當更新 module library 版本時透過 module path 定義 ex: module shellab.io/gotestmod/v2 配合打上合適的 tag
>     - 以 Semantic Versioning 來看 **v{major_version}.{minor_version}.{patch_version}** golang 在更新時只會動 minor_version, patch_version 而 major_version 視為不同的 library
>     - 使用 **go get shellab.io/gotestmod/v2** 來裝
>     - 在實作一個 module 時可以使用相同 libary 的不同版本，為了必免 conflict 只要給它個 alias 就可以解決了
>         - ```go=
>           import (
>               "shellab.io/gotestmod"
>               gotestmod_v2 "shellab.io/gotestmod/v2"
>           )
>           ```
> - go module path 不定義 version 靠上 tag
>     - 不在 module path 中定義不同版本，只打上不同的 tag ,則裝完之後出現 **+incompatible** 告知目前版本不會向下相容，你的程式可能會爛掉
>         - ex: github.com/natefinch/lumberjack v2.0.0+incompatible 
>     - 直接透過 go get @version 來裝
>     - 使用 **go get shellab.io/gotstmod@v2.1.2** 來裝
> - [version control 的機制](https://mileslin.github.io/2020/08/Golang/%E5%88%B0%E5%BA%95-go-get-%E7%9A%84%E7%89%88%E8%99%9F%E6%80%8E%E9%BA%BC%E9%81%8B%E4%BD%9C%E7%9A%84/)

```bash=
> mkdir go_major_version && cd go_major_version
> go mod init go_major_version
> go get -u gitlab.com/shellab/golang/gotestmod
> go get -u gitlab.com/shellab/golang/gotestmod/v2
```
go.mod
```go=
module goversion

go 1.18

require (
	gitlab.com/shellab/golang/gotestmod v1.2.1
	gitlab.com/shellab/golang/gotestmod/v2 v2.0.1
)
```
main.go
```go=
// main module 使用同個 package 的不同版本
package main

import (
	"gitlab.com/shellab/golang/gotestmod/foo"
	v2_myfoo "gitlab.com/shellab/golang/gotestmod/v2/foo" // 避免 conflic 給 alias
)

func main() {
	myfoo.ShowFoo()
	v2_myfoo.ShowFoo()
}
```
### 使用 go tool (1.24+) 來管工具相依性


- `go get -tool import_path@version`
    - ex: go get -tool github.com/swaggo/swag/cmd/swag@v1.16.5
    - ex: go get -tool aqwari.net/xml/cmd/xsdgen
- `go list tool` 來顯示裝那些相依套件
![image](https://hackmd.io/_uploads/SkXEN2vqeg.png)
- `go tool xsdgen -version` 執行 xsdgen cmd
- `go tool -n xsdgen` 印出該 cmd 存在什麼地方

#### 刪除工具相依

> 直接到 go.mod 把 tool () 中不需要的刪除，再做 ==go mod tidy==

## Unit test

```
// unit test layout
.
├── car
│   ├── car.go
│   └── car_test.go
├── go.mod
├── go.sum
└── main.go
```
> - 將測試放在同個 package 下，以 **_test.go** 命名檔案
>     - ex: upload_file_validation_handler_test.go
> - 所有測試案例都以 **Test** 為 Prefix func name
>     - ex: TestFindMimetypeMedia

### Go test cmdline

> - go test -cover -v ./...
>     - 整個Module 中所有的 test 包含算出 coverage 還有測試細節結果
> - go test -cover -v ./pkg/service/...
>     - Module 中的某個 service package
> - go test -coverprofile=prof.out ./...
>     - 產生 coverage profile 可以用來轉換成 coverage report
> - go tool cover -html=prof.out -o report.html
>     - 透過 coverage profile 產生 coverage report

```go=
package handler

import (
    "testing"

    "github.com/gabriel-vasile/mimetype"

    "evergreen-workflow/pkg/imagemagick"
    "evergreen-workflow/pkg/mediainfo"
)

func TestFindMimetypeMedia(t *testing.T) {
    videoPath := "../../test/data/sample_video.mp4"
    video, _ := mediainfo.NewMediainfoFromFile(videoPath)
    videoMimeType, _ := mimetype.DetectFile(videoPath)

    type result struct {
        mimetype string
        err      error
    }
    // cases provider
    var tests = []struct {
        userDefinedType string
        mediainfo       *mediainfo.Media
        mimetype        *mimetype.MIME
        result          result
    }{
        {
            userDefinedType: "video",
            mediainfo:       video,
            mimetype:        videoMimeType,
            result: result{
                mimetype: "video/mp4",
                err:      nil,
            },
        },
    }
    // for all the cases test result
    for _, test := range tests {
        result, err := findMimetypeMedia(test.userDefinedType, test.mimetype.String(), test.mediainfo)
        if result != test.result.mimetype || err != test.result.err {
            t.Error("parse error")
        }
    }
}

func TestSha1Hash(t *testing.T) {
    sha1, _ := sha1Hash("../../test/data/sample_video.mp4")
    if sha1 != "1385f0808b7ef1600b6c39244917ef1df2ce6652" {
        t.Error("sha1 hash is incorrect")
    }
}

func TestDecodeKeyPath(t *testing.T) {
    _, _, _, err := decodeKeyPath("video-xxxxxxx.mp4")
    if err == nil {
        t.Error("key path should parse error")
    }
}

func TestCheckImageDimension(t *testing.T) {
    image, _ := imagemagick.NewImageMagickFromFile("../../test/data/sample_graphic.webp")
    result := checkImageDimension(image)
    if result == true {
        t.Error("image is not fit to the spec, should return false")
    }
}

func TestCheckImageAspectRatio(t *testing.T) {
    image, _ := imagemagick.NewImageMagickFromFile("../../test/data/sample_graphic.webp")
    result := checkImageAspectRatio("landscape", image)
    if result == true {
        t.Error("image is not match landscape spec, should return false")
    }
}
```

## Design Package

> - package 分兩種
>     - main package
>         - 為應用程式執行的 entry point
>         - 只能有一個
>         - 沒有 main package 無法編譯成可執行的檔案
>             - go build
>                 - 會依 go.mod 中 module name 編出「執行檔名」
>                 - ex: gitlab.com/dachichang/gotestmain => gotestmain (執行檔名)
>             - go install
>                 - 裝安到 $GOPATH/bin 下面
>     - library package
>         - 不可以執行，但是可以給可執行的 main package 做使用
> - 要注意
>     - 目錄下同級檔案屬於同一個 package
>     - package name 和目錄名稱可以不一樣，但是正常來說應該要設一樣
>     - 大寫開頭：在 package 之外可見，稱作 exported
>     - 小寫開頭：僅限於 package 內使用，稱作 unexported


> package layout (gotestmod)
> - go.mod (gitlab.com/dachichang/gotestmod)
> - foo/ (import path name gitlab.com/dachichang/gotestmod/foo)
>     - bar/ (import path name gitlab.com/dachichang/gotestmod/foo/bar)
>         - b.go (檔名不重要 pakcage bar 其它使用該 pakcage 要用 bar.xxx)
>         - c.go (同為 pakcage bar，可以直接用 b.go 中的定義)
>     - a.go (檔名不重要, package myfoo 其它使用該 package 要用 myfoo.xxx, 不是 foo. import 只是定位「地址」而使用 package 要依 package 的定義，通常會定義成一樣的)
> - space/ (import path name gitlab.com/dachichang/gotestmod/space)
>     - point.go (檔名不重要, package space 其它使用該 package 要用 space.xxx)
>     - point_3d.go

[範例在這裡](https://gitlab.com/dachichang/gotestmod)
```bash=
# create a module 
mkdir gotestmod
cd gotestmod # 目錄名稱不重要，但是應該要和 mod name (gotestmod) 一樣
go mod init gitlab.com/dachichang/gotestmod #${MODULE_ADDR}/{MODULE_NAME} 這個 gotestmod 為 build 出來的名稱

.
├── README.md
├── foo
│   ├── a.go
│   └── bar
│       ├── b.go
│       └── c.go
├── go.mod
└── space
    ├── point.go
    └── point_3d.go
```
```go=
//  go.mod
module gitlab.com/dachichang/gotestmod

go 1.1
```
```go=
// foo/a.go
package myfoo

import (
    "fmt"
    "gitlab.com/dachichang/gotestmod/foo/bar"
)

func ShowFoo() {
    fmt.Println("pakcage myfoo, func ShowFoo !!!")
    fmt.Println("call bar.ReturnShowBar", bar.ReturnShowBar())
}
```
```go=
// foo/bar/b.go
package bar

func ReturnShowBar() string {
    return "package bar, func ReturnShowBar and return string"
}

func showBar() string {
    return "bar private function!!"
}
```
```go=
// foo/bar/c.go
package bar

import (
    "fmt"
)

func ShowPrivateBar() {
    fmt.Println("hello, world", showBar())
}
```
```go=
// foo/space/point.go
package space

import (
    "fmt"
)

type IPoint interface { // public
    X() int
    Y() int
    SetX(int)
    SetY(int)
}

type point struct { // private, invisable from the other package
    x int
    y int
}

func NewPoint(x int, y int) IPoint { // construct, return interface (interface is a pointer)
    p := &point{
        x: x,
        y: y,
    }
    return p
}

func (p *point) String() string { // implement debug
    return fmt.Sprintf("x: %d y: %d", p.x, p.y)
}

func (p *point) X() int { // implement get property
    return p.x
}

func (p *point) Y() int { // implement get property
    return p.y
}

func (p *point) SetX(x int) { // implement set property
    p.x = x
}

func (p *point) SetY(y int) { // implement set property
    p.y = y
}
```
```go=
// foo/space/point_3d.go
package space

import (
    "fmt"
)

// Point3D

type IPoint3D interface {
    IPoint // extends interface
    Z() int
    SetZ(int)
    Show()
}

type Point3D struct { // public, visable from the other package
    point// extends from the point struct
    z int
}

func NewPoint3D(x int, y int, z int) *Point3D { // return struct pointer
    p := &Point3D{}
    p.SetX(x)
    p.SetY(y)
    p.SetZ(z)
    // or
    /*
    p := &Point3D{
        point: point{
            x: x,
            y: y,
        },
        z: z,
    }
    */
    // or
    /*
    p := &Point3D{}
    p.x = x
    p.y = y
    p.z = z
    */
    return p
}

func (p *Point3D) Z() int {
    return p.z
}

func (p *Point3D) SetZ(z int) {
    p.z = z
}

func (p *Point3D) Show() { // override show method
    fmt.Println("X:", p.point.X(), "Y:", p.point.Y(), "Z:", p.Z())
}

func (p *Point3D) String() string {
    return fmt.Sprintf("x: %d y: %d z: %d", p.x, p.y, p.z)
}
```

## IO package

> - Golang 對 IO 讀寫提供很多的 package 各有目的
>     - io
>         - 定義一些基本接口 interface 和 const
>         - 像是常見的 Reader, Writer interface
>         - io.EOF const
>     - ioutil
>         - io 的工具包，比較實用的功能
>         - ReadAll, ReadFile, WriteFile, ReadDir
>         - 它的實作可以看到它都是呼叫 io, os 等 package 做一些整合工具
>     - bufio
>         - 在 io package 上面多封裝一層，加了緩存功能，常和 ioutil, bytes.Buffer 搞混
>         - 它和 ioutil 差異在於**緩存**在讀大檔案時候，可以一段一段的讀到 memory 而 ioutil 讀完可能會 OOM
>         - 它和 bytes.Buffer 差異在於，bufio 用在**文件**到**記憶體**，而不是**記憶體**到**記憶體**
>     - bytes, strings
>         - 兩個差不多都且實現了 Reader ，只是操作對象不同一個是 string 一個是 bytes

### IO interface

```go=
// defined for io read, write interface
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}
```
### strings.Reader

```go=
// strings.Reader Read implements the io.Reader interface.
type Reader struct {
    s        string
    i        int64 // current reading index
    prevRune int   // index of previous rune; or < 0
}

func NewReader(s string) *Reader {
    return &Reader{s, 0, -1}
}

func (r *Reader) Read(b []byte) (n int, err error) {
    if r.i >= int64(len(r.s)) {
        return 0, io.EOF
    }
    r.prevRune = -1
    n = copy(b, r.s[r.i:])
    r.i += int64(n)
    return
}

```

### io/ioutil package (deprecated by os package)

> **因為是一次性讀寫，所以文件的內容不能過大，方便用於小文件的一次讀入和寫出**

> **未來將棄用所有功能在 os package 都可以找到**

- file
    - ioutil.ReadAll -> io.ReadAll
    - ioutil.ReadFile -> os.ReadFile
    - ioutil.ReadDir -> os.ReadDir
    - ioutil.WriteFile -> os.WriteFile
- others
    - ioutil.NopCloser -> io.NopCloser
    - ioutil.ReadDir -> os.ReadDir
    - ioutil.TempDir -> os.MkdirTemp
    - ioutil.TempFile -> os.CreateTemp

```go=
// 都是 wrapper function
func ReadAll(r io.Reader) ([]byte, error) {
    return io.ReadAll(r)
}

func ReadFile(filename string) ([]byte, error) {
    return os.ReadFile(filename)
}

func WriteFile(filename string, data []byte, perm fs.FileMode) error {
    return os.WriteFile(filename, data, perm)
}

func ReadDir(dirname string) ([]fs.FileInfo, error) {
    f, err := os.Open(dirname)
    if err != nil {
        return nil, err
    }
    list, err := f.Readdir(-1)
    f.Close()
    if err != nil {
        return nil, err
    }
    sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
    return list, nil
}
```

### bufio package

> - Reader 和 Scanner 差不多可以替代使用
>     - Reader
>         - 讀到長度 4k
>     - Scanner
>         - 可讀到長度 64k
>         - 具有**斷詞**功能，給定 token pattern 可以自動分段讀取

```go=
// bufio.Reader
func main() {
    file, err := os.Open("./testFile.txt")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    defer file.Close()
    // file 有實作 Read() 功能 io.Reader interface
    reader := bufio.NewReader(file)
    for {
        line, err := reader.ReadString('!')
        // 讀入的 line 有包含了 "!" 不需要的話要自已排除
        if err != nil {
            if err == io.EOF {
                break
            } else {
                fmt.Println(err)
                os.Exit(1)
            }
        }
        fmt.Print(line)
    }
}
```
```go=
// bufil.Scanner
// The simplest use of a Scanner, to read standard input as a set of lines.
func ExampleScanner_lines() {
    scanner := bufio.NewScanner(os.Stdin)
    // 預設的 splitFunc 是 bufio.ScanLines
    for scanner.Scan() { // 讀入不包含換行, EOF 或是 Error 時停下來
        fmt.Println(scanner.Text()) // Println will add back the final '\n'
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading standard input:", err)
    }
}

// Use a Scanner to implement a simple word-count utility by scanning the
// input as a sequence of space-delimited tokens.
func ExampleScanner_words() {
    // An artificial input source.
    const input = "Now is the winter of our discontent,\nMade glorious summer by this sun of York.\n"
    scanner := bufio.NewScanner(strings.NewReader(input))
    // Set the split function for the scanning operation.
    scanner.Split(bufio.ScanWords)
    // Count the words.
    count := 0
    for scanner.Scan() { //掃到錯誤停下來，或是到 EOF
        count++
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading input:", err)
    }
    fmt.Printf("%d\n", count)
    // Output: 15
}

// Use a Scanner with a custom split function (built by wrapping ScanWords) to validate
// 32-bit decimal input.
func ExampleScanner_custom() {
    // An artificial input source.
    const input = "1234 5678 1234567901234567890"
    scanner := bufio.NewScanner(strings.NewReader(input))
    // Create a custom split function by wrapping the existing ScanWords function.
    split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        advance, token, err = bufio.ScanWords(data, atEOF)
        if err == nil && token != nil {
            _, err = strconv.ParseInt(string(token), 10, 32)
        }
        return
    }
    // Set the split function for the scanning operation.
    scanner.Split(split)
    // Validate the input
    for scanner.Scan() {
        fmt.Printf("%s\n", scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        fmt.Printf("Invalid input: %s", err)
    }
    // Output:
    // 1234
    // 5678
    // Invalid input: strconv.ParseInt: parsing "1234567901234567890": value out of range
}
```


## Useful Algorithm

### Sort

- [simple quick sort video](https://www.youtube.com/watch?v=kFeXwkgnQ9U)
```go=
// quick sort
package main

import "fmt"

func main() {
    data := []int{10, 5, 25, 351, 14, 9}

    fmt.Printf("%#v", quickSort(data))
}

func quickSort(data []int) []int {
    if len(data) <= 1 {
        return data
    }
    lastIdx := len(data) - 1
    pivot := data[lastIdx] // the last one

    data = data[:lastIdx]

    itemGreater := []int{}
    itemLower := []int{}

    for _, v := range data {
        if v > pivot {
            itemGreater = append(itemGreater, v)
        } else {
            itemLower = append(itemLower, v)
        }
    }

    lowerResult := quickSort(itemLower)
    greaterResult := quickSort(itemGreater)
    result := append(lowerResult, pivot)
    result = append(result, greaterResult...)
    return result
}
```

### Collection Functions
```go=
// 找出 slice 中符合 target 在那個欄位
func index(data []string, target string) int {
    for i, v := range data {
        if v == target {
            return i
        }
    }
    return -1
}

// slice 包含是否資料，或是是否存在資料
func include(data []string, target string) bool {
    return index(data, target) >= 0
}

// 透過 callback func 來 filter out 要的資料
func filter(data []string, f func(string) bool) []string {
    var resultData []string
    for _, v := range data {
        if f(v) {
            resultData = append(resultData, v)
        }
    }
    return resultData
}

// 將 slice 中每個資料都套用 func 並存到新的 slice 去
func dataMap(data []string, f func(string) string) []string {
    resultData := make([]string, len(data)) // 每個都要做，已知輸出的 slice 大小用 make 先做出來
    // 用 make 做出 cap ，會比 append 效率好
    for i, v := range data {
        resultData[i] = f(v)
    }
    return resultData
}

func main() {
    strs := []string{"peach", "apple", "pear", "plum"}

    fmt.Println(index(strs, "pear"))
    fmt.Println(include(strs, "plum"))
    fmt.Println(filter(strs, func(s string) bool {
        return len(s) > 4
    }))
    fmt.Println(filter(strs, func(s string) bool {
        return strings.Contains(s, "u")
    }))
    fmt.Println(dataMap(strs, func(s string) string {
        return strings.ToUpper(s)
    }))
}

```

## Design Pattern

### Interface Polymorphism
```go=
package storage

import (
    "context"
    "io"

    "cloud.google.com/go/storage"
)

// storage interface
type Storage interface {
    Upload(content io.ReadCloser, path string) error
    Download(path string) (io.ReadCloser, error)
}

// implementation struct
type GoogleStorageImpl struct {
    client *storage.Client
    Bucket string
}

// Constructor for the implementation struct
func NewGoogleStorage(bucket string) (*GoogleStorageImpl, error) {
    gcsClient, err := storage.NewClient(context.Background())
    if err != nil {
        return nil, err
    }

    return &GoogleStorageImpl{
        client: gcsClient,
        Bucket: bucket,
    }, nil
}

// implement interface Upload
func (g *GoogleStorageImpl) Upload(content io.ReadCloser, path string) error {
    defer content.Close()

    uploadObj := g.client.Bucket(g.Bucket).Object(path)
    w := uploadObj.NewWriter(context.TODO())
    defer w.Close()

    _, err := io.Copy(w, content)
    if err != nil {
        return err
    }

    return nil
}

// implement interface Download
func (g *GoogleStorageImpl) Download(path string) (io.ReadCloser, error) {
    reader, err := g.client.Bucket(g.Bucket).Object(path).NewReader(context.TODO())
    if err != nil {
        return nil, err
    }
    return reader, nil
}
```

### Functional Options
```go=
type Server struct {
    host string
    port int
    secure bool
    timeout time.Duration
}

// 每個 option 都是處理 Serve struct 的一種設定
type Option func(*Server)

// 當 create object 時可以給 1~N 個 Option
func NewServer(opts ...Option) *Server {
    // 預設 config
    srv := &Server{
        host: "localhost",
        port: 8080,
        timeout: 30 * time.Second,
    }
    // 依每個 Option 套上
    for _, opt := range opts {
        opt(srv)
    }
    return srv
}

// WithHost configures the Server's host
func WithHost(host string) Option {
    return func(s *Server) {
        // 可以再做一些處理
        s.host = host
    }
}

// WithPort configures the Server's port
func WithPort(port int) Option {
    return func(s *Server) {
        // 可以再做一些處理
        s.port = port
    }
}

// Usage
srv := NewServer(
    WithHost("example.com"),
    WithPort(9090),
)
```

### Singleton
```go=
package database

import (
    "fmt"
    "os"
    "sync"

    log "github.com/sirupsen/logrus"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"

    "evergreen-backend/pkg/environment"
)

var once sync.Once
var singletonDB *gorm.DB

func GetDB() *gorm.DB {
    once.Do(func() {
        dsn := fmt.Sprintf("host=%s user=%s password=%s port=%d dbname=%s",
            os.Getenv("DB_HOST"),
            os.Getenv("DB_USER"),
            os.Getenv("DB_PASSWD"),
            5432,
            os.Getenv("DB_NAME"),
        )
        db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
        if err != nil {
            log.Fatalf("Can't connect to database with DSN: %s", dsn)
        }
        runtimeEnv := environment.GetEnvironment(os.Getenv("ENV"))
        if runtimeEnv == environment.DEV {
            // when in dev environment default enable db debug mode
            singletonDB = db.Debug()
        } else {
            singletonDB = db
        }
    })
    return singletonDB
}

func MigrateVersion() uint {
    db := GetDB()
    migrateInfo := struct {
        Version uint
    }{}
    db.Table("schema_migrations").Select("version").First(&migrateInfo)
    return migrateInfo.Version
}
```

## Tools

### gofmt

> 幫忙格式化內容

- gofmt routers/router.go | diff -u routers/router.go 
    - 比較看看格式改了什麼
- gofmt -w example.go
    - 直接正規化蓋掉
- gofmt -w .
    - 對以下整個目標做正規化蓋掉


## Example

- [Go By eample](https://gobyexample.com/)

```go=
package main
import "fmt"
func main() {
    fmt.Println("hello golang")
}
```

## Debug

### Escape Analysis

> varialbe 分佈，要麻是 stack, 不然就是 heap 而如果變數無法在 scope 被釋放，就只能配到 heap 去，所以可以進行逃逸分析來了解是否對 GC 造成巨大壓力

```bash=
go build -gcflags=-m . > output.txt 2>&1 
```

### OOM

> golang 透過 GC 回收 memory 可以設定 GOMEMLIMIT 來限制最大 memory 用量，避免 OOM

- [GOMEMLIMIT](https://hsleep.medium.com/optimizing-memory-management-in-go-with-gomemlimit-da8ed23e2dcb)

## Document

- [Printf](https://golang.org/pkg/fmt/)
- [Deepin array and slice](https://www.sohamkamani.com/golang/arrays-vs-slices/)
- [各種go地雷的分析用法](https://pjchender.dev/golang/pointers)

## Topic Document

- [OAuth2 PKCE (Proof Key for Code Exchange) for line login](https://engineering.linecorp.com/zh-hant/blog/pkce-line-login/) 
- [pseudo-versions](https://jfrog.com/blog/go-big-with-pseudo-versions-and-gocenter/)
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments)
- [Go Modules with private git repo](https://medium.com/cloud-native-the-gathering/go-modules-with-private-git-repositories-dfe795068db4)
- [為什麼 Pointer Receiver 不能使用 Value Type 賦值給 Interface Value](https://mileslin.github.io/2020/08/Golang/%E7%82%BA%E4%BB%80%E9%BA%BC-Pointer-Receiver-%E4%B8%8D%E8%83%BD%E4%BD%BF%E7%94%A8-Value-Type-%E8%B3%A6%E5%80%BC%E7%B5%A6-Interface-Value/)
- [HowTo High performance http client](https://www.loginradius.com/blog/async/tune-the-go-http-client-for-high-performance/)
- [Understanding golang nil](https://www.jianshu.com/p/dd80f6be7969)
- [Channel use cases and pattern](https://go101.org/article/channel-use-cases.html)
- [Golang struct receiver should use Value or Pointer](https://blog.wu-boy.com/2017/05/go-struct-method-pointer-or-value/)
- [Golang JWT Encode/Decode](https://medium.com/%E4%BC%81%E9%B5%9D%E4%B9%9F%E6%87%82%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88/golang-json-web-tokens-jwt-olang-json-web-tokens-jwt-%E7%A4%BA%E7%AF%84-225b377e0f79)
- [Golang by example](https://golangbyexample.com/)
- [Accept interface, return structs](https://bryanftan.medium.com/accept-interfaces-return-structs-in-go-d4cab29a301b)
- [Go module upgrade](https://golang.cafe/blog/how-to-upgrade-golang-dependencies.html)
- [Go Repository pattern](https://medium.easyread.co/golang-clean-archithecture-efd6d7c43047)
- [Go Interpreter Pattern](https://medium.com/@MTrax/golang-interpreter-pattern-411e6d5e4a30)
- [Go Design Pattern](https://medium.com/towardsdev/desing-patters-in-golang-24a142d2cc91)
- [Go Design Pattern 2](https://medium.com/selectfrom/golang-design-patterns-in-kubernetes-6e74d6caba4)