Jex’s Note

Golang Basic

宣告

[string]{ .. }

elements := map[string]map[string]string{
    "A": map[string]string{
        "field1": "val1",
        "field2": "val2",
    },
    "B": map[string]string{
        "field1": "val1",
        "field2": "val2",
    },
}

[string]int

map := map[string]int{"apple": 5, "lettuce": 7}

[string]interface{}

x := map[string]interface{}{
    "foo": []string{"a","b"},
    "bar": "foo",
    "baz": 10.4,
}

[]interface{}

hnapCommandMap := make(map[string]interface{})
hnapCommandMap["ID"] = "06"
hnapCommandMap["Info"] = map[string]string{
    "Name":       "Jex"
}

resque2 := map[string]interface{}{
    "class": "hnap",
    "args":  []interface{}{hnapCommandMap},
}

map[string]interface{}

方法1
a := map[string]interface{}{
    "int":    1,
    "string": "xx",
}

方法2
t := map[string]interface{}{}
t["id"] = 312
t["type"] = "realtime"
t["data"] = []map[string]string{
    {
        "did":    "did1",
        "action": "action1",
    },
    {
        "did":    "did2",
        "action": "action2",
    },
}

但不可以, 無法這樣給值
var t map[string]interface{}
t["xxx"] = "xxx"

宣告 slice struct ([]struct)

type Target struct {
    Topic   string `json:"topic"`
    Message string `json:"message"`
}
type Payload struct {
    Targets []Target `json:"targets"`
    From    string   `json:"from"`
}

var payload = Payload{
    Targets: []Target{
        Target{
            Topic:   "topic1",
            Message: "message1",
        },
        Target{
            Topic:   "topic2",
            Message: "message2",
        },
    },
    From: "api",
}

宣告空 slice, var VS :=

宣告出來為 nil, 長度 0 (建議)

var q []string

宣告出來為 [], 長度 0, 如果要 json encode 想避免 null 的話, 建議使用這個

q := []string{}

其他

var t *T = new(T)   // or  t := new(T)
var a uint64 = 22   // 等於 a := uint64(22)
a := 0x12           // 等於 18

const 對應 int (有點像 enum)

const (
    LevelCritical = iota    // 0
    LevelError
    LevelWarning
    LevelNotice
    LevelInfo               // 4
    LevelDebug
)

指標

  • & : 對變數取址
  • * : 對指針取值, 將指標取回一般變數

Example :

package main
import "fmt"
func main() {
    var a int = 1
    var b *int = &a
    var c **int = &b
    var x int = *b
    fmt.Println("a = ",a)                           // 1
    fmt.Println("&a = ",&a)                         // &a =  0xf840037100
    fmt.Println("*&a = ",*&a)                       // *&a =  1
    fmt.Println("b = ",b)                           // b =  0xf840037100
    fmt.Println("&b = ",&b)                         // &b =  0xf840037108
    fmt.Println("*&b = ",*&b)                       // *&b =  0xf840037100
    fmt.Println("*b = ",*b)                         // *b =  1
    fmt.Println("c = ",c)                           // c =  0xf840037108
    fmt.Println("*c = ",*c)                         // *c =  0xf840037100
    fmt.Println("&c = ",&c)                         // &c =  0xf840037110
    fmt.Println("*&c = ",*&c)                       // *&c =  0xf840037108
    fmt.Println("**c = ",**c)                       // **c =  1
    fmt.Println("***&*&*&*&c = ",***&*&*&*&*&c)     // ***&*&*&*&c =  1
    fmt.Println("x = ",x)                           // x =  1
}

將指標轉成實體, 讓 code 更乾淨

func ArgsToJob(Args *map[string]interface{}) Job {
    args := (map[string]interface{})(*Args)
    job := Job{
        ID:          args["ID"].(string),
    }
    return job
}

將 interface{} 轉成 map[string]interface{}

a := (map[string]interface{})(args[0].(interface{}).(map[string]interface{}))

什麼時候用指標?

參考 code :

type User struct {
  Name string
}

func main() {
  u := &User{Name: "Leto"}
  println(u.Name)
  Modify(u)
  println(u.Name)
}

func Modify(u *User) {
  u = &User{Name: "Paul"}
}

如果是比較大的結構,在宣告時可以用指標以節省記憶體, 但高階語言慢慢不用指標了,所以其實也可以不用特別宣告用指標, 用結構 call function 用結構傳遞,傳遞參數給 function 的話則不需要特別用指標,

指標行為

code :

func main() {
    a := QQ{}
    b := &a
    c := *b

    fmt.Printf("a: %p\n", &a)
    fmt.Printf("b: %p (address same as a) \n", b)
    fmt.Printf("c: %p (different address from a, b)\n", &c)

    d := xx()
    fmt.Printf("d: %p (different address from x)\n", &d)

    e := zz()
    fmt.Printf("e: %p (different address from z)\n", &e)
}

func xx() QQ {
    x := QQ{}
    fmt.Printf("x: %p (return instance)\n", &x)
    return x
}

func zz() *QQ {
    z := QQ{}
    fmt.Printf("z: %p (return pointer)\n", &z)
    return &z
}

如果 struct 定義沒有欄位, 結果 :

type QQ struct {}

a: 0x1127a88
b: 0x1127a88 (address same as a)
c: 0x1127a88 (different address from a, b)
x: 0x1127a88 (return instance)
d: 0x1127a88 (different address from x)
z: 0x1127a88 (return pointer)
e: 0xc42000c030 (different address from z)

如果 struct 定義有欄位, 結果 :

type QQ struct {
    Name string
}

a: 0xc42000e290
b: 0xc42000e290 (address same as a)
c: 0xc42000e2a0 (different address from a, b)
x: 0xc42000e2e0 (return instance)
d: 0xc42000e2d0 (different address from x)
z: 0xc42000e2f0 (return pointer)
e: 0xc42000c030 (different address from z)

關於回傳 instance or pointer 的記憶體 : call func 拿到的都是新的記憶體, 不管 func 裡面回傳的是不是指標

swtich

map + switch

m := map[string]int{"foo":1}
f := func(key string) bool { _, ok := m[key]; return ok }
switch {
    case f(key):
        // whatever

or

switch category {
case
    "auto",
    "news",
    "sport",
    "music":
    return true
}

斷言 (type assertion)

分辨型別

var anything interface{} = "string"
switch v := anything.(type) {
case string:
    fmt.Println(v)
case int32, int64:
    fmt.Println(v)
case interface{}:
    fmt.Println(v)
default:
    fmt.Println("unknown")
}

已知道是什麼型別

value, ok := a.(string)
if !ok {
    fmt.Println("It's not ok for type string")
    return
}

// or
if str, ok := a.(string); ok {

轉型

int to int64

i64 := int64(23)

int to float64

i := 5;
f := float64(i)

int to string

s := strconv.Itoa(123)

int64 to string

s := strconv.FormatInt(int64(5), 10)

int64 to float64

float64(1)

int64 to uint64

u, err := strconv.ParseUint(strconv.FormatInt(int64(123), 10), 10, 64)

float64 to string

s64 := strconv.FormatFloat(v, 'E', -1, 64)

float64 to int64

var f float64 = 55.3
i = int64(f)

float64 to uint

uint(user["age"].(float64))

string to byte

dd := "dcf"
fmt.Println([]byte(dd))

result : [100 99 102]

string to int

v, err = strconv.Atoi(s)

string to int64

v, err := strconv.ParseInt(s, 10, 64)

string to uint32/uint64

v := "42"
if s, err := strconv.ParseUint(v, 10, 32); err == nil {
    fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseUint(v, 10, 64); err == nil {
    fmt.Printf("%T, %v\n", s, s)
}

string to float64

v, err := strconv.ParseFloat("55.74", 64)

*string to string

value := *pointer_str

byte to string

s := string(byteArray)
s := string(byteArray[:])

array to slice

x[:]

指標(pointer)轉實體

*f is msg := sqs.Message(*m)

(*f)["cc"]
or
(map[string]interface{})(*f)["cc"]
or
msg := sqs.Message(*m)

interface{} to int

a := job["retryTimes"].(int)

[]interface{} 轉成 interface{}

interface{}([]interface{}{reactor, sensor})

[]interface{} to []int ref : InterfaceSlice

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
    interfaceSlice[i] = d  // 或用 append 的方式
}

interface{} conver to bytes.Buffer

switch v := any.(type) {
case bytes.Buffer:          // return as is
    return v.String()       // Here v is of type bytes.Buffer
}

interfacer{} (Server) to (Server)

type Server struct {
    Name string
}

var v interface{}
if v == nil {
    v = &Server{Name: "xx"}
}
fmt.Println(reflect.TypeOf(v))      // *main.Server
s := v.(*Server)
fmt.Println(s.Name)                 // xx

map[string]interface{} 變成陣列 []interface{}

[]interface{}{reactor, sensor}

struct to another struct

type A struct{ Name string }
type B struct{ Name string }
a := A{"aa"}
b := B(a)
b.Name = "bb"
fmt.Println(a)
fmt.Println(b)

bytes to io.Reader

bytes.NewReader(b)

strings to io.Reader

strings.NewReader(s)

file to bytes

b, err := ioutil.ReadFile("/tmp/ff.tmp")

file to io.Writer

file, err = os.Open("/tmp/ff.tmp")
defer file.Close()

loop

Infinite

for {

}

Range, like other languages

for i:=1; i<=5; i++ {

}

Until the specific time

for time.Now().Unix() < 1481089195 {
    time.Sleep(1 * time.Second)
}

slice

slice結構

結構

     []byte
     ---------
ptr  | *elem |
     ---------
len  |  int  |
     ---------
cap  |  int  |
     ---------

a slice with five elements

s := make([]byte, 5)  // 指定 len, 不指定 cap

     []byte
     ---------
ptr  |       |  -> [5] bytes | 0 | 0 | 0 | 0 | 0 |
     ---------
len  |  5    |
     ---------
cap  |  5    |
     ---------

nil slice

var s []byte

     []byte
     ---------
ptr  |  nil  |
     ---------
len  |   0   |
     ---------
cap  |   0   |
     ---------

基本操作

append item

list = append(list, item)

append slice

list = append(list, list2...)

其他

重新切一個 slice, 新 slice 會使用原本 slice 的底層, 改成使用 copy 才會是一個新的 slice

func main() {
    data := get()
    fmt.Println(len(data), cap(data), &data[0])
}

func get() []byte {
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0])
    return raw[:3]
}
// 10000 10000 0xc42005e000
// 3 10000 0xc42005e000     (記憶體位置一樣)

func get() (res []byte) {
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0])
    res = make([]byte, 3)
    copy(res, raw[:3])
    return
}
// 10000 10000 0xc42005e000
// 3 3 0xc42000e280

傳遞 slice 是傳遞記憶體位置

感謝 chris:

  • 記得 slice map 的變數 只是一個參照
  • slice 跟 map 的變數 參照的 值都是隔開的
  • 但是底層運作是使用 指標
  • 裡面真正的值 是存在記憶體
  • 關鍵字: 淺拷貝/深拷貝

example

func main() {
    d := []string{"xxx"}
    fmt.Println("original:", d)
    nonPointer(d)
    fmt.Println("non-pointer:", d)
    change(d)
    fmt.Println("change:", d)
    pointer(&d)
    fmt.Println("pointer:", d)
}

func nonPointer(d []string) {
    d = append(d, "vvvv")
}

func change(d []string) {
    d[0] = "ccc"                // 可修改到外部
}

func pointer(d *[]string) {
    *d = append(*d, "vvvv")
}

結果

original: [xxx]
non-pointer: [xxx]
change: [ccc]
pointer: [ccc vvvv]

宣告獨立乾淨的 slice

不論是參數傳遞 (call by value) 還是 assign 給一個新的變數, *elem (pointer) 都會指向同一個, 而改其中一個也會改動到另一個

如果沒有 append 新的 item, 只改變原本 slice 長度內的值 e.g. s[2] = 3 (如果s總長度是5), 都會一直是共用指標

如果要打破這個規則, 就將其中一個 append 新的 item 那麼 *elem (pointer) 就會是不同的

如果要將 slice 指給一個新的變數, 但又不想要將原本的 *elem (pointer) 參照到原本的變數, 除了用 make 也可以 new 全新的再用 append 給值

code

s := []int{1, 2, 3}

// Old *elem
s1 := s

// New *elem
s2 := s
s2 = append(s2, 4)

// New *elem
s3 := []int{}
s3 = append(s3, s...)

// New *elem
s4 := make([]int, 0)
s4 = append(s4, s...)

s[1] = 999

fmt.Println(s)      // [1 999 3]
fmt.Println(s1)     // [1 999 3]
fmt.Println(s2)     // [1 2 3 4]
fmt.Println(s3)     // [1 2 3]
fmt.Println(s4)     // [1 2 3]

array vs slice

差別主要是 array 是有固定長度, slice 沒有

code:

// array
var List = [2]int{1, 2}  // or [...]int{1,2}
copyList := List
copyList[1] = 4
fmt.Printf("new=%v, old=%v\n", copyList, List)

// slice
var oldSlice = []int{1, 2, 3, 4}
newSlice := oldSlice
newSlice[3] = 10
newSlice = append(newSlice, 100)
fmt.Printf("new=%v, old=%v\n", newSlice, oldSlice)

result:

new=[1 4], old=[1 2]
new=[1 2 3 10 100], old=[1 2 3 10]

array 是直接操作記憶體位置

map misc

傳入接收 actionData map[string]interface{} 參數的 func

send_var(jobData["ActionData"].(map[string]interface{}))

func send_var(actionData []interface{}) (err error) {
    fmt.Println(actionData[0].(map[string]interface{})["Did"])

[]map[string]interface{} 傳入型態接受 []interface{} 的 func

test(&map[string]interface{})
...
func test(rec *[]interface{})

用指標方式傳入可避免型態轉換發生的問題

map[string]interface{} 一直輸出它的 index 順序不一定會一樣

qq := map[string]interface{}{
    "0": "111111",
    "1": "xxxxxxxx",
}

[]interface{} 一直輸出它的 index 順序會一樣

用 mi 定義 type, 讓 code 更乾淨

type mi map[string]interface{}
res["Envelope"].(interface{}).(mi)["Body"].(interface{}).(mi)[actionData["Name"].(string)+"Response"].(interface{})

assignment to entry in nil map : 會發生此原因是你在賦值給 map 時沒有初始化

var d []map[string]interface{}

(Wrong)
    var f map[string][]map[string]interface{}
    f["ss"] = d

(Correct)
    f := map[string][]map[string]interface{}{}
    f["ss"] = d

(Correct)
    var f map[string][]map[string]interface{}
    f = make(map[string][]map[string]interface{})
    f["ss"] = d

map 是否為空

if len(map) == 0 {
    ....
}

判斷 key 是否存在, 如果不存在不會造成 error

a := map[string]interface{}{
    "a": "A",
}

if t1, matched := a["a"]; matched {
    fmt.Println(t1.(string))
}

if t2 := a["a"]; t2 != nil {
    fmt.Println(t2.(string))
}

判斷 key 及型態

a := map[string]interface{}{"fff": "xx"}
ff, ok := a["fff"].(int)   // 即使來源跟對象的型態不一樣, 不會造成 panic
if !ok {
    fmt.Println("no")
    // return
}
fmt.Println(ff)

// result
no
0

Slice Tricks

判斷 slice key 是否存在

a := []string{"zero", "one", "two"}
fmt.Println(a, len(a))
x, v := 3, "nothing"
if len(a)-1 >= x  {
    v = a[x]
}
fmt.Printf("%s", v)

type *map[string]interface {} does not support indexing

func tt(dd *map[string]interface{}) {
    (*dd)["qq"] = "qqqq"
}

傳遞 map 是傳址非傳值

func main() {
    dd := map[string]interface{}{
        "ff": "ffff",
    }
    fmt.Printf("original, mem: %p val: %v\n", &dd, dd)
    nonPointer(dd)
    fmt.Printf("non-pointer, val: %v\n", dd)
    change(dd)
    fmt.Printf("change, val: %v\n", dd)
    pointer(&dd)
    fmt.Printf("pointer, val: %v\n", dd)
    newMap(dd)
    fmt.Printf("newMap, val: %v\n", dd)
    makeMap(dd)
    fmt.Printf("makeMap, val: %v\n", dd)
}

// 會修改到外部
func nonPointer(dd map[string]interface{}) {
    fmt.Printf("non-pointer, mem: %p\n", &dd)
    dd["dd"] = "ddd"
}

// 會修改到外部
func change(dd map[string]interface{}) {
    fmt.Printf("non-pointer, mem: %p\n", &dd)
    dd["ff"] = "cccc"
}

// 會修改到外部
func pointer(dd *map[string]interface{}) {
    fmt.Printf("pointer, mem: %p\n", &dd)
    (*dd)["qq"] = "qqqq"
}

// 即使 qq 裡, new 一個新變數 ff, 並將 dd 的值給 ff, 修改仍會改到外面傳進來的 map
func newMap(dd map[string]interface{}) {
    ff := dd
    ff["ff"] = "zzzz"
}

// 必須要用 make new 一個實體, 才不會參照到原本的記憶體位址
func makeMap(dd map[string]interface{}) {
    cc := make(map[string]interface{})
    for k, v := range dd {
        cc[k] = v
    }
    cc["bbb"] = "bbb"
}

結果

original, mem: 0xc42000c028 val: map[ff:ffff]
non-pointer, mem: 0xc42000c038
non-pointer, val: map[ff:ffff dd:ddd]
non-pointer, mem: 0xc42000c040
change, val: map[ff:cccc dd:ddd]
pointer, mem: 0xc42000c048
pointer, val: map[ff:cccc dd:ddd qq:qqqq]
newMap, val: map[dd:ddd qq:qqqq ff:zzzz]
makeMap, val: map[dd:ddd qq:qqqq ff:zzzz]

func params

傳入/接收 map pointer

error: (type *map[string]interface {} does not support indexing)

solution:

func main() {
    a := map[string]interface{}{
        "xxx": "XXX",
    }

    qq(&a)
}

func qq(tmp *map[string]interface{}) {
    fmt.Println((*tmp)["xxx"])
}

傳入/接收 map pointer

var args = map[string]interface{}{
    "A": "1",
    "B": "2",
}
foo(&args)
func foo(args *map[string]interface{}) {
    options := (map[string]interface{})(*args)
    fmt.Println(options["A"])
}

foo(args)
func foo(name string, ...args map[string]interface{}) {
    options := (map[string]interface{})(args[0])
    fmt.Println(options["A"])
}

傳入 Optional 參數

T1("A", "B")
func T1(str ...string) {
    fmt.Println(str[0]) // A
}

T2([]string{"A", "B"})
func T2(str ...[]string) {
    fmt.Println(str[0]) // [A B]
}

map

test("test")
test("test", map[string]interface{}{"dd": "DD", "cc": "CC"})

func test(required string, optional ...map[string]interface{}) {
    fmt.Println(required)
    fmt.Println(len(optional))
    if len(optional) > 0 {
        if val, existed := optional[0]["dd"]; existed {
            fmt.Println(val)
        }
    }
}

將 slice params 傳入 args…

func echo(strings ...string) {
    for _, s := range strings {
        fmt.Println(s)
    }
}

func main() {
    strings := []string{"a", "b", "c"}
    echo(strings...) // Treat input to function as variadic
}

interface & struct

[struct] 成員大小寫

跟 func 一樣,大寫代表 public,小寫是 private

[struct] map

type AWS struct {
   SQS  map[string]*aws_sqs.SQS
}

var qq AWS
qq.SQS = make(map[string]*aws_sqs.SQS)
qq.SQS[xx] = &ws_sqs.SQS{ ... }

[struct] function pointer

type Job struct {
    Done func() error
}

var job Job
job.Done = done

func done() error {

}

[struct] 定義 + 賦值

resp := Music{
    Genre: struct {
        Country string
        Rock    string
    }{
        Country: "Taylor Swift",
        Rock:    "Aimee",
    },
}

Assign a struct to an interface

type Interface interface{}

type Struct struct{}

func main() {
        var ps *Struct
        var pi *Interface
        pi = new(Interface)
        *pi = ps

        _, _ = pi, ps
}

關於 interface 及 struct 的用法

package main

import "fmt"

# 用 interface 定義一個抽象層,只負責說這個類別有什麼動作
type PeopleAction interface {
    AnimalAction
    Stand()
}

# 本身也可以被繼承
type AnimalAction interface {
    Run()
    Eat()
}

# 定義資料結構
type Animal struct {
    Name string
}

func (animal *Animal) Run() {
    fmt.Println(animal.Name + " is running")
}

func (animal *Animal) Eat() {
    fmt.Println(animal.Name + " is eatting")
}

func (animal *Animal) Stand() {
    fmt.Println(animal.Name + " is standing")
}

func main() {
    var jex PeopleAction = &Animal{"Jex"}
    var bob AnimalAction = &Animal{"Bob"}
    jex.Run()               // Jex is running
    jex.Eat()               // Jex is eatting
    jex.Stand()             // Jex is standing
    bob.Run()               // Bob is running
    bob.Eat()               // Bob is eatting
    // bob.Stand()          // bob.Stand()  Error : bob.Stand undefined (type AnimalAction has no field or method Stand)
}

Receive interface as param

type A interface{}

type B struct {
    A
    Age int
}

func main() {
    var a A
    fmt.Println("interface A type: ", reflect.TypeOf(a))
    b := B{a, 30}
    fmt.Println("struct B type: ", reflect.TypeOf(b))
    Show(b)
}

func Show(a A) {
    fmt.Println("show age: ", a.(B).Age)
    fmt.Println("interface param type: ", reflect.TypeOf(a.(B)))
}

Result

interface A type:  <nil>
struct B type:  main.B
show age:  30
interface param type:  main.B

Type T satisfies interface I defined in the first snippet. Values of type T can be f.ex. passed to any function accepting I as a parameter

type I interface {
    M() string
}
type T struct {
    name string
}
func (t T) M() string {
    return t.name
}
func Hello(i I) {
    fmt.Printf("Hi, my name is %s\n", i.M())
}
func main() {
    Hello(T{name: "Michał"}) // "Hi, my name is Michał"
}

single type can implement many interfaces

type I1 interface {
    M1()
}
type I2 interface {
    M2()
}
type T struct{}
func (T) M1() { fmt.Println("T.M1") }
func (T) M2() { fmt.Println("T.M2") }
func f1(i I1) { i.M1() }
func f2(i I2) { i.M2() }
func main() {
    t := T{}
    f1(t) // "T.M1"
    f2(t) // "T.M2"
}

same interface, different structs, call their own function with same name

type I interface {
    M() (string, error)
}
type T1 struct{ Name string }
type T2 struct{ Name string }

func main() {
    // Different structs call its own func
    t1, _ := M(&T1{Name: "T1"})
    fmt.Println(t1)
    t2, _ := M(&T2{Name: "T2"})
    fmt.Println(t2)

    // Declare an interface and assign different sturcts to it
    var Q I
    v1 := &T1{Name: "T3"}
    Q = v1
    t3, _ := Q.M()
    fmt.Println(t3)

    v2 := &T2{Name: "T4"}
    Q = v2
    t4, _ := Q.M()
    fmt.Println(t4)
}

func M(i I) (string, error)      { return i.M() }
func (t *T1) M() (string, error) { return t.Name, nil }
func (t *T2) M() (string, error) { return t.Name, nil }

same interface, different structs, call same func

No, you can’t!

struct + json

json.Marshal(struct {
        Data     *MyStruct   `json:"data,omitempty"`
        Status   string      `json:"status,omitempty"`
        Reason   string      `json:"reason,omitempty"`
    }{
        Data:   &r.Data,
        Status: r.Status,
        Reason: r.Reason,
    })
}

Assign func

type Job struct {
    ID string
    Do func(*Job)
}

func main() {
    var j1 = &Job{ID: "J01", Do: do}
    var j2 = &Job{ID: "J02", Do: do}
    j1.Do(j1)
    j2.Do(j2)
}

func do(j *Job) {
    fmt.Println("Job ID:", j.ID)
}

巢狀

type Resp struct {
    UserID struct {
    } `json:"user_id"`
    Data struct {
        Info struct {
            Name        string   `json:"name"`
            Friends     []Friend `json:"friends"`
        } `json:"info"`
    } `json:"data"`
}

輸出

"user_id": {},
"data": {
    "info": {
        "name": "XXX",
        "friends": [
            { },
            { },
        ],
    },
},

JSON tag 補充

// 接收跟輸出都會忽略
Field int `json:"-"`

// Field appears in JSON as key "myName" and the field is omitted from the object if its value is empty, as defined above.
Field int `json:"myName,omitempty"`

// Field appears in JSON as key "Field" (the default), but the field is skipped if empty.
Field int `json:",omitempty"`

type Ticket struct {
    Places []Place `json:"places"`  // json 傳過來時的 places 也要是陣列
    Name string `json:"name"`       // 單純接收字串
    UID string                      // 注意! 即使沒設定 tag, 如果傳進來是 "uid": "123" 這樣也會 map 得到
    FriendID string                 // 如果傳進來是 "friend_id": "123" 這樣 map 不到
}

Nested struct 即使有設 omitempty 也 json 仍會輸出 "field": {}, 解決方法是指定 pointerstruct

type A struct { Val  L2 `json:"val,omitempty"` }
type B struct { Val *L2 `json:"val,omitempty"` }        // pointer 是關鍵
type L2 struct { Val string `json:"val,omitempty"` }

json.Marshal 輸出 :

a := &A{Val: L2{Val: ""}}   // {"val":{}}
a := &A{}                   // {"val":{}}
a := &B{}                   // {}

避免空的 slice json marshal 後為 null

var users []map[string]interface{}
if users == nil {
    users = make([]map[string]interface{}, 0)
}

import

運作流程

當 import A 時, 會先執行 A 的 init(), 但如果 A 裡面還有 import B, 則會優先執行 B 的 init(),

當 package 的 init() 都執行完後, 才會執行 main 的 init(), 最後才會執行 main()

import 的幾種類型

1) 正常引入

import(
    "fmt"
)

這是我們平常最常引入的方法

2) 省略前綴的 package name

import (
    . "fmt"
)

ex : fmt.Println("hello world") 可以省略寫成 Println("hello world")

3) Alias

import (
    f "fmt"
)

ex : f.Println("hello world")

4) 不引用 package 內部函數

import (
    _ "fmt"
)

當引入這個 package 只會執行它的 init()

套件管理

dep (官方)

  • dep init: 第一次才會用到, 會產生 Gopkg.toml, Gopkg.lock (不要理它), vendor (安裝好 package)
  • dep ensure: install, Gopkg.toml 裡面設定的 package. 另外如果要新增沒有 tag 的版本到 Gopkg.toml 也是用這個指令, 後面不用指定套件, 它會自動去掃程式
  • dep ensure -update: 升級所有套件到最新的
  • dep ensure -add github/…/../errors@{tag or commit id}: 新增套到 Gopkg.toml / Gopkg.lock and vendor
  • dep status: 顯示你目前的版本跟最新的版本
  • 升級某個套件版本要改 Gopkg.toml, 再執行 dep ensure

Vendor

govendor,用起來挺直覺的,如果使用過 npm 或 gem 的經驗,它的概念是一樣的

govendor cheat sheet

第一次使用

govendor init               # 會產生 vendor/vendor.json, 但裡面是空的
govendor add +external      # 幫你把目前使用到 GOPATH 套件版本填到 vendor.json,也會產生 github.com, etc. 第三方套件

第 N 次使用, 更新 govendor.json

govendor remove +v          # 清空 `vendor.json` 及刪除 `github.com`, .. etc.
govendor add +external      # 再重加有使用到的套件

依據 vendor/vendor.json 使用到的套件從 local 的 GOPATH 加到 vendor/, 如果 local 沒有要先下 go get

govendor sync

如果 github 某個套件更新了, 想更新 vendor 這個套件

govendor fetch golang.org/x/net/context
Error: Remotes failed for

如果有個 local repo (private repo) 加不進 vendor/

$ govendor sync
Error: Remotes failed for:
        Failed for "relog" (failed to ping remote repo): unrecognized import path "relog"

直接 add 那個 local repo

govendor add relog
升級某一套件版本
govendor fetch github.com/satori/go.uuid

(不要採用, 僅保留)

  1. 先從 govendor 移除掉 govendor remove github.com/parnurzeal/gorequest
  2. 移除 local 的 github.com/parnurzeal/gorequest
  3. go get github.com/parnurzeal/gorequest
  4. govendor add +external 更新到 vendor.json
不管怎麼重 build, code 一直是舊的

有可能你將目前專案加到 vendor/ 下了, 把它刪掉, 再重編就可以了

Panic and Recover

攔截並印出 error

如果不想因為程式發生 index out of range 或由 panic 擲出的 error 導致整個程序強制中止的話可以用 recover 來攔截, 另外有時候程式的 error message 不夠明確時, 用 recover 有時候可以得到較明確的訊息

defer func() {
    if e := recover(); e != nil {
        // e is the interface{} typed-value we passed to panic()
        fmt.Println("Whoops: ", e) // Prints "Whoops: boom!"
    }
}()
var qq []string
fmt.Println(qq[0]) // 或直接用 panic("boom!")
fmt.Println("This will never be called")

recover() 要寫在最前面,否則會無法攔截到

將 stack 裡的東西印出來

func main() {
    defer func() {
        if e := recover(); e != nil {
            for i := 1; ; i++ {
                _, file, line, ok := runtime.Caller(i)
                if !ok {
                    break
                }
                log.Println(fmt.Sprintf("%s:%d", file, line))
            }
        }
    }()
    qq() // :21
    log.Println("Done!")
}

func qq() {
    ff() // :26
}

func ff() {
    panic("ff") // :30
}

Result:

2017/09/20 19:16:22 /usr/local/go/src/runtime/asm_amd64.s:514
2017/09/20 19:16:22 /usr/local/go/src/runtime/panic.go:489
2017/09/20 19:16:22 /tmp/qq.go:30
2017/09/20 19:16:22 /tmp/qq.go:26
2017/09/20 19:16:22 /tmp/qq.go:21
2017/09/20 19:16:22 /usr/local/go/src/runtime/proc.go:185
2017/09/20 19:16:22 /usr/local/go/src/runtime/asm_amd64.s:2197

其他

tag

type User struct {
    _    struct{} `type:"structure"`
    name string   `json:"name-field"`
    age  int
}

func main() {
    user := &User{name: "John Doe The Fourth", age: 20}

    if field, ok := reflect.TypeOf(user).Elem().FieldByName("_"); ok {
        fmt.Println(field.Tag)
    } else {
        panic("Field not found")
    }

    if field, ok := reflect.TypeOf(user).Elem().FieldByName("name"); ok {
        fmt.Println(field.Tag)
    } else {
        panic("Field not found")
    }
}

結果:

type:"structure"
json:"name-field"

runtime

  • The runtime library implements garbage collection, concurrency, stack management, and other critical features of the Go language.
  • The Go runtime manages scheduling, garbage collection, and the runtime environment for goroutines.

Pointer guildeline

  • Slices, maps and channels are reference types that do not require the extra indirection of an allocation with new
  • If the receiver is large, a big struct for instance, it will be much cheaper to use a pointer receiver.
  • If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used.
  • For types such as basic types, slices, and small structs, a value receiver is very cheap so unless the semantics of the method requires a pointer, a value receiver is efficient and clear.

盡量減少在其他子 package 用 var 定義全域變數

多使用全域來定義 struct, func, const,不得以才用 var 定義全域變數;如果有共用的 func 都會用到共同的變數,就用 struct 定義,否則容易產生 Memory Leak 的問題

go env

$ go env
GOARCH="amd64"
GOBIN=""
GOCHAR="6"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH=""
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
CC="gcc"
GOGCCFLAGS="-g -O2 -fPIC -m64 -pthread -fno-common"
CGO_ENABLED="1"

show variable type

import "reflect"
fmt.Println(reflect.TypeOf(tst))

判斷 interface{} 是否是 slice

if reflect.TypeOf(test["slice"]).Kind() == reflect.Slice {

}

debug

如同 PHP 的 print_r(), var_dump(), var_export(), 如果型態是 map 它也會把 key value 印出來

fmt.Printf("%v", whatever)

用上面的不會 struct 的欄位名稱,如果印出 struct 的欄位及值要用 :

fmt.Printf("%+v", whatever)

string literals are character sequences

string("Hello"[1])                  // ASCII only       e
string([]rune("Hello, 世界")[1])    // UTF-8            e
string([]rune("Hello, 世界")[8])    // UTF-8            界

指令

  • syntax checking : gofmt -e xx.go
  • 透過傳遞參數的方式改變 main.go 裡面 var 變數 : go build -ldflags '-X main.BUILD_ID=1'
  • go build 時太多 error 時你只會看到幾行然候其他就被省略成 too many errors, 改用 go build -gcflags="-e" 就可印出全部的 error

在 if 裡面 new 參數無法傳遞到外面

裡面有 new

var a bool
if true {
    a := true
    fmt.Println(a)
}
fmt.Println(a)

true
false

裡面沒 new

var a bool
if true {
    a = true
    fmt.Println(a)
}
fmt.Println(a)

true
true

MySQL NULL

null

string sql.NullString
int64 sql.NullInt64
float64 sql.NullFloat64

time 加上 tag

time.Time `sql:"default:null"`

或是用指標

string *string

Comments