[pit you may step on when you get started-02]

Posted by MVSS on Fri, 31 Dec 2021 13:42:37 +0100

Go language is a simple and interesting programming language. Like other languages, it will inevitably encounter many pits when used, but most of them are not the design defects of go itself. If you have just moved to go from another language, this article may help you. Designed to help you skip these pits and reduce a lot of debugging time.

Go entry may step on the pit-01 We have summarized one part of the. Now let's learn another part.

16. The value of type string is a constant and cannot be changed

It is not allowed to try to use the index to traverse the string to update individual characters in the string.

The value of string type is a read-only binary byte slice. If you want to modify the characters in the string, convert the string to [] byte. After modification, convert it to string.

// Error example
func main() {
    x := "text"
    x[0] = "T"		// error : cannot assign to x[0]
    fmt.Println(x)
}

// Modification example
func main() {
    x := "text"
    xBytes := []byte(x)
    xBytes[0] = "T" 		// At this point, T is the rune type
    x = string(xBytes)
    fmt.Println(x)
}

Note: the above example is not the correct posture for updating the string, because a UTF8 encoded character pool can occupy multiple bytes. For example, Chinese characters need 3-4 bytes to store. At this time, it is wrong to update one byte. The correct way is to convert a string to a run slice (at this time, a run may occupy multiple bytes) and directly update the characters in the run.

func main() {
    x := "text"
    xBytes := []rune(x)
    xBytes[0] = "you" 		
    x = string(xBytes)
    fmt.Println(x)		// You ext
}

17. Conversion between string and byte slice

When string and byte slice are converted to each other, the original value of the copy is involved in the conversion. This conversion process is different from the mandatory type conversion in other programming languages, and also different from the underlying array shared by the new slice and the old slice.

Go optimizes the conversion between string and byte slice to avoid additional memory allocation:

  • When searching for the key in map[string], the corresponding [] byte is used to avoid the memory allocation of m[string(key)]
  • For I, V: = range [] byte (STR) {}

18. string and index operator

Using index access to a string returns not a character, but a byte value. This is handled in the same way as other languages.

func main() {
    x := "ascii"
    fmt.Println(x[0])  		// 97
    fmt.Printf("%T", x[0])	// unit8
}

19. Not all strings are UTF8 text

The value of string does not have to be UTF8 text and can contain any value. Only when the string is literal, it is UTF8 text. The string can contain other data by escape. To determine whether the string is UTF8 text, you can use the ValidString() function in the "unicode/utf8" package

func main() {
    str1 := "ABC"
    fmt.Println(utf8.ValidString(str1)) // true
    
    str2 := "A\xdw"
    fmt.Println(utf8.ValidString(str2))  // false
    
    str3 := "A\\ssc"
    fmt.Println(utf8.ValidString(str3))	// true escapes escape characters to literal values
}

20. Length of string

The built-in function len() of Go returns the number of byte s of the string. If you want to get the number of characters of the string, you can use RuneCountInString(str string) (n int) in the "unicode/utf8" package

func main() {
    char := "heart"
    fmt.Println(utf8.RuneCountInString(char))  
}

// Note: RuneCountInString does not always return the number of characters seen, because some characters will occupy two runes

func main() {
    char := "é"
    fmt.Println(len(char))    // 3
    fmt.Println(utf8.RuneCountInString(char))    // 2
    fmt.Println("cafe\u0301")    // caf é / / French cafe is actually a combination of two rune s
}

21. The number is missing in multiline array, slice and map statements

func main() {
    x := []int{
        1, 
        2 	// syntax error : unexceted newline, expecting comma or }
    }
    
    y := []int{1,2}
    z := []int{1,2}
}

After} folding to a single line in a declaration statement, the tail is not required.

22. log.Fatal and log Panic is not just a log

The log standard library provides different logging levels. Different from the log libraries of other languages, the log package of Go can do more things outside the log when calling Fatal() and Panic(), such as interrupting the execution of programs

func main() {
    log.Fatal("fatal level log : log entry")  	// After outputting the information, the program terminates execution
    log.Println("panic level log : log entry")
}

23. Operations on built-in data structures are not synchronized

Although Go itself has a large number of features to support concurrency, it does not guarantee the safety of concurrent data. Users need to ensure that variables and other data are updated by atomic operation.

24. range iteration map

If you want to iterate the map in a specific order (such as sorting by key), note that each iteration may produce different results.

The run time of Go is intended to disrupt the iterative order, so the iterative results may be inconsistent. However, it is not always disrupted, and it is also possible to obtain the same results continuously, for example:

func main() {
    m := map[string]int{"one" :1, "two" :2, "three":3, "four" : 4}
    for k, v := range m {
        fmt.Println(k, v)
    }
}

25. fallthrough statement in switch

The case code block in the Switch statement will take break by default, but you can use fallthrough to force the execution of the next case code block

func main() {
    isSpace := func(char byte) bool {
        switch char {
        case ' ' :			// The space character will directly break and return false  
            // fallthrough
        case '\t' :
        	return true    
        }
        return false
    }
    
    
    fmt.Println(isSpace('\t'))   // true
    fmt.Println(isSpace(' '))    // false
}

It can also be rewritten as case multi condition judgment:

func main() {
    isSpace := func(char byte) bool {
        switch char {
        case ' ', '\t' :			
        	return true
        }
        return false
    }
    
    
    fmt.Println(isSpace('\t'))   // true
    fmt.Println(isSpace(' '))    // false
}

26. Self increasing and self decreasing operations

Many programming languages come with pre post + + - operations. However, Go has its own way, removing the pre operation, and using + + - values as operators rather than expressions.

// Error example
func main() {
    data := []int{1,2,3}
    i := 0
    ++i 				// syntax error: unexpected ++, expecting }
    fmt.Println(data[i++])		// syntax error : unexpected ++, expecting :
}

// Correct example
func main() {
    data := []int{1,2,3}
    i := 0
    i++
    fmt.Println(data[i])  // 2
}

27. Reverse by bit

Many programming languages use ~ as a unary bitwise negation (NOT) operator. Go reuses the ^ XOR operator to bitwise negation

// Error example
func main() {
    fmt.Println(~2)   // bitwise complement operator is ^
}

// Correct example
func main() {
    var d uint8 = 2
    fmt.Printf("%08b\n", d)
    fmt.Printf("%08d\n", ^d)
}

^ is also a bitwise XOR (XOR) operator.

28. Operator priority

In addition to the bit clear operator, Go also has many bit operators like other languages, but the priority is another matter.

Priority list:

Precedence           Operator
5						* / % << >> & &^
4						+ - | ^
3						== != < <= > >=
2						&&
1						||

29. struct fields that are not exported cannot be encode d

Field members starting with lowercase letters cannot be directly accessed externally, so when struct encode s json, xml, gob and other formats, these private fields will be ignored and zero values will be obtained everywhere.

func main() {
    type MyData struct{
        One int `json:"One"`
        two string `json:"two"`  
    }
    
    in := MyData{1, "two"}
    fmt.Printf("%#v\n", in)			// main.MyData{One:1, two:"two"}
	
    encode, _ := json.Marshal(in)
    fmt.Println(string(encode))     // {"One":1}
	
    var out MyData
    json.Unmarshal(encod, &out)
    fmt.Println("%#v\n")			// main.MyData{One:1, two:""}
}

30. When the program exits, goroutine is still executing

By default, the program does not exit until all goroutine s are executed. This needs special attention:

// The main program will exit directly
func main() {
    workerCount := 2
    for i := 0; i < workerCount; i++ {
        go doIt(i)
    }
    
    time.Sleep(1 * time.Second)
    fmt.Println("all done")
}

func doIt(workerId int) {
    fmt.Printf("[%v] is running\n", workerID)
    time.Sleep(3 * time.Second)        // Simulation goroutine is executing
    fmt.Printf("[%v] is done\n", workerID)
}

31. Send data to the unbuffered channel and return as soon as the receiver is ready

The sender will block only when the data is processed by the receiver. Depending on the running environment, after the sender sends the data, the goroutine of the receiver may not have enough time to process the next data.

func main() {
    ch := make(chan string)
    
    go func() {
        for m := range ch {
            fmt.Println("processed:", m)
            time.Sleep(1 * time.Second)
        }  
    }()
    
    ch <- "cmd.1"
    ch <- "cmd.2"		
}

32. Sending data to a closed channel will cause panic

It is safe to receive data from a closed channel. When the reception status value ok is false, it indicates that there is no data to receive in the channel. Similarly, when data is received from a buffered channel and no data is available after the cached data is obtained, the status value is also false.

Sending data to a closed channel will cause panic:

func main() {
    ch := make(chan int)
    for i := 0; i < 3; i++ {
        go func(idx int) {
           ch <- idx 
        }(i)
    }
    
    fmt.Println(<-ch)
    close(ch)
    time.Sleep(2 * time.Second)
}

33. A channel with a value of nil is used

Sending and receiving data on a channel with a value of nil will be permanently blocked

func main() {
    var ch chan int    // Uninitialized, value nil
    
    for i := 0; i<3; i++{
        go func(i int){
           ch <- i 
        }(i)
    }
    
    fmt.Println("result:", <- ch)
    time.Sleep(1 * time.Second)
}

34. If the parameter passed by the function receiver is a value transfer method, the original value of the parameter cannot be modified

The parameter of the method receiver is similar to that of a general function: if it is declared as a value, the method body will get a copy of the value of the parameter. At this time, any modification of the parameter will not affect the original value. Unless the receiver parameter is a variable of map or slice type, and the fields in the map are updated by pointer, the elements in slice will update the original value.

type data struct {
    num   int
    key   *string
    items map[string]bool
}

func (this *data) pointerFunc() {
    this.num = 7
}

func (this data) valueFunc() {
    this.num = 8
    *this.key = "valueFunc.key"
    this.items["valueFunc"] = true
}

func main() {
    key := "key1"

    d := data{1, &key, make(map[string]bool)}
    fmt.Printf("num=%v  key=%v  items=%v\n", d.num, *d.key, d.items)

    d.pointerFunc()    // Modify the value of num to 7
    fmt.Printf("num=%v  key=%v  items=%v\n", d.num, *d.key, d.items)

    d.valueFunc()    // Modify the values of key and items
    fmt.Printf("num=%v  key=%v  items=%v\n", d.num, *d.key, d.items)
}

Topics: Go Back-end