50 Shades of Go: Traps, Gotchas, and Common Mistakes, translated by author KCQon.

Not long ago, I found this high quality article in Zhihu, and I plan to translate it again with my own understanding. The essays are divided into three parts: beginner 1-34, Intermediate 35-50, and advanced 51-57

preface

Go is a simple and fun programming language, and like other languages, there are a lot of bugs to use, but most of them are not design flaws of Go itself. If you’re new to Go from another language, you’ll probably miss this one.

If you spend time studying official doc’s, wikis, discussion mailing lists, Rob Pike’s numerous articles, and Go’s source code, you’ll find that the potholes in this article are common, and beginners can skip them to save a lot of time debugging code.

Elementary Chapter: 1-34

1. Open curly brace{You can’t have a single line

In most other languages, the position of {is up to you. Go is a special type of code that has automatic semicolon injection. The compiler adds a delimiter to the end of each line. To separate multiple statements, such as a semicolon after a) :

Func main() {println("hello world")} // Equivalent to func main(); Println ("hello world")}Copy the code

./main.go: missing function body

./main.go: syntax error: unexpected semicolon or newline before {

Func main() {println("hello world")}Copy the code

2. Unused variables

If there are unused variables in the function body code, this will not compile, but it is possible to declare global variables but not use them.

Even if you assign a value to a variable after it is declared, it still won’t compile and needs to be used somewhere:

Var gvar int () {var one int: one declared and not used two := 2 var one declared and not used two := 2 two declared and not used var three int // error: Three declared and not used three = 3} func main() {var one int _ = one two := 2 println(two) var three int one = three var four int four = four }Copy the code

3. Unused imports

If you import a package and use none of its variables, functions, interfaces, and constructs, the compilation will fail.

You can use the _ underscore symbol as an alias to ignore imported packages to avoid compilation errors, which simply execute init() of the package

// Imported and not used: "FMT" "log" // Imported and not used: "log" "time" // imported and not used: "Time") func main() {} // Correct example // You can use the Goimports tool to annotate or remove unused packages import (_ "FMT" "log" "time") func main() {_ = log.Println _ = time.Now }Copy the code

4. Short-declared variables can only be used inside functions

Myvar := 1 // syntax error: Non-declaration statement outside function body func main() {} var myvar = 1 func main() {}Copy the code

5. Use short declarations to repeatedly declare variables

It is not possible to duplicate a single variable with a short declaration. If there is at least one new variable to the left, multiple variable declarations are allowed:

Func main() {one := 0 one := 1 // error: Func main() {one := 0 one, two := 1, 2 // Two is a new variable that allows one to be declared repeatedly. Err one, two = two, oneCopy the code

6. You cannot use short declarations to set field values

Struct variable fields cannot be assigned using := to use predefined variables to avoid resolution:

Type info struct {result int} func work() (int, error) {return 3, nil } func main() { var data info data.result, err := work() // error: non-name data.result on left side of := fmt.Printf("info: %+v\n", data)} // Correct example func main() {var data info var err error // err needs to be declared data.result, err = work() if err! = nil { fmt.Println(err) return } fmt.Printf("info: %+v\n", data) }Copy the code

7. Accidentally overwriting variables

Short declarations are useful for developers moving from dynamic languages, which can be misleading := is an assignment operator.

If you misuse a new block of code like the following :=, the compilation will not report an error, but the variable will not work as expected :=

Func main() {x := 1 println(x) // 1 {println(x) // 1 x := 2 println(x) // 2 // New x variable scope only inside the code block} println(x) // 1}Copy the code

This is a common mistake that Go developers make, and it’s not easy to spot.

Vet tools can be used to diagnose this variable coverage. Go does not do coverage checking by default. Add the -shadow option to enable it:

> go tool vet -shadow main.go
main.go:9: declaration of "x" shadows declaration at main.go:5
Copy the code

Note that VET does not report all variables covered. Go-nyet can be used for further testing:

> $GOPATH/bin/go-nyet main.go
main.go:10:3:Shadowing variable `x`
Copy the code

8. Variables of explicit type cannot be initialized with nil

Nil is the default initial value for variables of the interface, Function, Pointer, Map, Slice, and channel types. However, the declaration does not specify a type, and the compiler cannot infer the specific type of the variable.

Func main() {var x = nil // error: Func main() {var x interface{} = nil _ = x}Copy the code

9. Use slice, map with value nil

Adding elements to a slice with a value of nil is allowed, but adding elements to a map with a value of nil causes a runtime panic

Func main() {var m map[string]int m["one"] = 1 // error: panic: Assignment to nil map := make(map[string]int) Func main() {var s []int s = append(s, 1)}Copy the code

The capacity of the map

You can specify the size when creating a variable of type map, but you cannot use cap() to detect the size of the allocated space, as slice does:

Func main() {m := make(map[string]int, 99) println(cap(m)) // error: invalid argument m1 (type map[string]int) for cap }Copy the code

11. The value of a string variable cannot be nil

For those who prefer to initialize strings with nil, this is a pit:

Func main() {var s string = nil // cannot use nil as type string in assignment if s == nil {// invalid operation: S == nil (mismatched types string and nil) s = "default"}} // Correct example func main() {var s string // The zero value of string type is an empty string "" if s == "" { s = "default" } }Copy the code

12. An Array value is used as a function parameter

In C/C++, arrays (names) are Pointers. When you pass an array as an argument to a function, you pass a reference to the memory address of the array, changing the value of the array inside the function.

In Go, arrays are values. When passed as an argument to a function, we pass a copy of the original array, which cannot be updated inside the function:

Func main() {x := [3]int{1,2,3} func(arr [3]int) {arr[0] = 7 ftt.println (arr) // [7 2,3]}(x) FMT.Println(x) // [1 2 3] // Not what you think [7 2 3]}Copy the code

If you want to modify the parameter array:

  • Pass a pointer to this array directly:
/ / reference will modify the original data func main () {x: = [3] int {1, 2, 3} func (arr * [3] int) {(* arr) [0] = 7 FMT. Println (arr) / / & [2, 3, 7]} (& x) fmt.Println(x) // [7 2 3] }Copy the code
  • Use slice directly: The original slice data (underlying array) is updated even if the function gets a copy of the value in slice.
// Modify slice's underlying array, Slice func main() {x := []int{1, 2, 3} func(arr []int) { arr[0] = 7 fmt.Println(x) // [7 2 3] }(x) fmt.Println(x) // [7 2 3] }Copy the code

13. Range mixed up return values while traversing slice and array

Unlike for-in and foreach traversals in other programming languages, range in Go generates two values during traversal, the first being the element index and the second the element value:

// Error example func main() {x := []string{"a", "b", "C"} for v: range x = {FMT. Println (v) / / 1, 2, 3}} / / correct sample func main () {x: = [] string {" a ", "b", "c"} for _, V := range x {// use _ discard index fmt.println (v)}}Copy the code

14. Slice and Array are actually one-dimensional data

It looks like Go supports multi-dimensional arrays and slices. you can create arrays of arrays and slices of slices, but it doesn’t.

For applications that rely on dynamic calculation of multidimensional array values, Go is not ideal in terms of performance and complexity.

You can create dynamic multidimensional arrays using raw one-dimensional arrays, “standalone” slices, and “shared underlying arrays” slices.

  1. Use raw one-dimensional arrays: do index checking, overflow detection, and reallocate memory when adding values when the array is full.

  2. There are two steps to using “standalone” slices:

  • Creating an external slice
  • Memory is allocated for each internal slice

    Note that the internal slices are independent of each other so that any increase or decrease in the internal slice does not affect the other slices

Func main() {x := 2 y := 4 table := make([][]int, func main() {x := 2 y := 4 table := make([][]int, x) for i := range table { table[i] = make([]int, y) } }Copy the code
  1. Use slices of the Share underlying array
  • Create a container, Slice, that holds the raw data
  • Create additional slices
  • Cutting the original slice to initialize the other slices
Func main() {h, w := 2, 4 raw := make([]int, h*w) for I := range raw {raw[I] = I} &raw[4]) // [0 12 3 4 5 6 7] 0xC420012120 table := make([][]int, h) for I := range table {// Table [1] = raw[1*4: 1*4 + 4] table[1*4: 1*4 + 4] = raw[1*4: 1*4 + 4] i*w + w] } fmt.Println(table, &table[1][0]) // [[0 1 2 3] [4 5 6 7]] 0xc420012120 }Copy the code

More references to multidimensional arrays

go-how-is-two-dimensional-arrays-memory-representation

what-is-a-concise-way-to-create-a-2d-slice-in-go

15. Access keys that do not exist in the map

Similar to other programming languages, we want to return nil if we access a key that does not exist in a map, as in PHP:

> php -r '$v = ["x"=>1, "y"=>2]; @var_dump($v["z"]); ' NULLCopy the code

Go returns the zero value of the corresponding data type of the element, such as nil, “”, false, and 0. The value operation always returns a value, so we cannot determine whether the key is in the map by the retrieved value.

Check if the key exists that can be accessed directly with map by checking the second parameter returned:

/ / the wrong key detection way func main () {x: map [string] string = {" one ":" 2 ", "two" : ""," three ":" 3 "} if v: = x (" two "); V == "" {fmt.println ("key two is no entry") // Empty string returned whether key two exists or not}} // Correct example func main() {x := map[string]string{"one": "2", "two": "", "three": "3"} if _, ok := x["two"]; ! ok { fmt.Println("key two is no entry") } }Copy the code

16. A string value is a constant and cannot be changed

It is not allowed to attempt to update individual characters in a string by iterating through the string with an index.

A string value is a read-only binary byte slice. If you want to modify characters in a string, convert string to []byte and then convert string to string:

Func main() {x := "text" x[0] = "T" // error: Cannot assign to x[0] fmt.println (x)} // Modify example func main() {x := "text" xBytes := []byte(x) xBytes[0] = 'T' // Note the T at this time X = string(xBytes) fmt.println (x) // Text}Copy the code

Note: The above example is not the correct way to update a string, because a UTF8-encoded character can be multiple bytes, such as Chinese characters, which need 3 or 4 bytes to be stored, and updating one of those bytes is incorrect.

Correct way to update a string: Convert a string to a Rune slice (where one rune may account for multiple bytes), directly updating characters in rune

Func main() {x := "text" xRunes := []rune(x) xRunes[0] = 'I' x = string(xRunes) ftt.println (x) // ext}Copy the code

17. Conversion between String and Byte Slice

When string and Byte slice are converted, the original copied value is converted. This conversion process differs from the casting operations of other programming languages and from the way new slice shares the underlying array with old slice.

Go optimizes the string/Byte slice conversion by two points, avoiding additional memory allocation:

  • inmap[string]Is used to find the key[]byteTo avoid doingm[string(key)]Memory allocation of
  • usefor rangeIterating string to []byte:for i,v := range []byte(str) {... }

Fog: See the original text

String and index operators

An indexed access to a string returns not a character, but a byte value.

This is done the same way as in other languages, such as PHP:

< span style =" max-width: 100%; clear: both; min-height: 1em; var_dump($name); String (6) "中文" > PHP -r '$name="中文"; var_dump($name[0]); �" > PHP -r '$name=" PHP "; var_dump($name[0].$name[1].$name[2]); 'string(3) "medium"Copy the code
func main() {
	x := "ascii"
	fmt.Println(x[0])		// 97
	fmt.Printf("%T\n", x[0])// uint8
}
Copy the code

If you need to use a for range iteration to access characters in a string (Unicode code point/rune), the standard library has a “Unicode/UTF8” package for UTF8 decoding. Utf8string also has handy library functions like func (s *String) At(I int) rune.

19. Not all strings are UTF8 text

The value of string need not be UTF8 text and can contain any value. The string is UTF8 text only if it is a literal literal, and the string can be escaped to contain other data.

To check if a string is a UTF8 text, use the ValidString() function in the unicode/ UTF8 package:

func main() { str1 := "ABC" fmt.Println(utf8.ValidString(str1)) // true str2 := "A\xfeC" fmt.Println(utf8.ValidString(str2)) // false str3 := "A\\xfeC" fmt.Println(utf8.ValidString(str3)) // true // Escape characters to literals}Copy the code

20. Length of the string

In Python:

Data = u'♥' print(len(data)) # 1Copy the code

However in Go:

Func main() {char := "♥" FMT.Println(len(char)) // 3}Copy the code

Go’s built-in function len() returns the number of bytes in the string, rather than counting Unicode characters as Python does.

To get the number of characters in a string, use RuneCountInString(STR string) (n int) from the unicode/ UTF8 package.

Func main() {char := "♥" ftt.println (utf8.runecountinString (char)) // 1}Copy the code

Note: RuneCountInString does not always return the number of characters we see, because some characters take up 2 runes:

Func main() {char := "e" fmt.println (len(char)) // 3 fmt.println (utf8.runecountinString (char)) // 2 Println("cafe\u0301") // cafe //Copy the code

Reference: normalization

21. Missing in multi-line array, slice, map statements.

func main() { x := []int { 1, 2 // syntax error: Y := []int{1,2,} z := []int{1,2} //... }Copy the code

Declaration statement} after folding to a single line, tail, not required.

22. log.Fatallog.PanicNot only the log

The log standard library provides different levels of logging. Unlike log libraries in other languages, the Go log package can do more things outside of logging, such as interrupt program execution, when calling Fatal*() or Panic*() :

Println("Nomal level log: log entry")} func main() {log.fatal ("Fatal level log: log entry")Copy the code

23. The operation of the built-in data structure is not synchronous

Although Go itself has a large number of features to support concurrency, it does not guarantee the security of concurrent data. Users need to ensure that data such as variables are updated atomically.

Goroutine and channel are good ways to do atomic operations, or use locks in the “sync” package.

24. Range Iterates over the value of string

The index obtained by range is the position of the first byte of the character value (Unicode point/rune). Unlike other programming languages, this index is not directly the position of the character in the string.

Note that one character can have more than one rune, such as the e in the French word cafe. Special characters can be manipulated using the norm package.

The for range iteration attempts to translate string to UTF8 text, and any invalid code points are directly represented by the 0XFFFD Rune (�) UNicode replacement character. If there is any non-UTF8 data in the string, the string should be saved as byte Slice before operation.

func main() { data := "A\xfe\x02\xff\x04" for _, v := range data { fmt.Printf("%#x ", V) // 0x41 0xfffd 0x2 0xFFfd 0x4 // error} for _, v := range []byte(data) {fmt.printf ("%#x ", V) // 0x41 0xfe 0x2 0xff 0x4Copy the code

25. Range Iterates map

If you want to iterate over a map in a particular order (such as sorting by key), be aware that each iteration may produce different results.

The Go runtime is intentionally out of order, so you may get inconsistent iteration results. However, it is not always disrupted, and it is also possible to obtain the same results of five consecutive iterations, such as:

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

If you run the above code repeatedly on Go Playground, the output will not change, it will only recompile if you update the code. The iteration order is scrambled after recompilation:

26. The Fallthrough statement in switch

The case block in the switch statement is broken by default, but you can use fallthrough to force the next case block.

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

However, you can use fallthrough at the end of the case block to force the next case block.

Can also rewrite the case for multi-conditional judgment:

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

27. Increment and decrement operations

Many programming languages have built-in ++ and — operations. But Go goes out of its way by removing the front operator and using ++ and – as operators rather than expressions.

Func main() {data := []int{1, 2, 3} I := 0 ++ I // syntax error: unexpected ++, expecting } fmt.Println(data[i++]) // syntax error: unexpected ++, expecting : } / / correct sample func main () {data: int [] = {1, 2, 3} I: = 0 i++ FMT. Println (data) [I] / / 2}Copy the code

28. Reverse by bit

Many programming languages use ~ as the unary bitwise inversion (NOT) operator, and Go reuses the ^ XOR operator for bitwise inversion:

Println(~2) // Bitwise main() {var d uint8 = 2 fmt.Printf("%08b\n", d) // 00000010 fmt.Printf("%08b\n", ^d) // 11111101 }Copy the code

^ is also the bitwise XOR operator.

The same operator can be reused twice because the unary NOT operation NOT 0x02 is identical to the binary XOR operation 0x22 XOR 0xFF.

Go also has a special operator AND NOT &^, which takes 1 for different bits.

func main() {
	var a uint8 = 0x82
	var b uint8 = 0x02
	fmt.Printf("%08b [A]\n", a)
	fmt.Printf("%08b [B]\n", b)

	fmt.Printf("%08b (NOT B)\n", ^b)
	fmt.Printf("%08b ^ %08b = %08b [B XOR 0xff]\n", b, 0xff, b^0xff)

	fmt.Printf("%08b ^ %08b = %08b [A XOR B]\n", a, b, a^b)
	fmt.Printf("%08b & %08b = %08b [A AND B]\n", a, b, a&b)
	fmt.Printf("%08b &^%08b = %08b [A 'AND NOT' B]\n", a, b, a&^b)
	fmt.Printf("%08b&(^%08b)= %08b [A AND (NOT B)]\n", a, b, a&(^b))
}
Copy the code
10000010 [A] 00000010 [B] 11111101 (NOT B) 00000010 ^ 11111111 = 11111101 [B XOR 0xff] 10000010 ^ 00000010 = 10000000 [A  XOR B] 10000010 & 00000010 = 00000010 [A AND B] 10000010 &^00000010 = 10000000 [A 'AND NOT' B] 10000010&(^00000010)= 10000000 [A AND (NOT B)]Copy the code

29. The precedence of the operator

In addition to the bit clear operator, Go has many of the same bit operators as other languages, but with a different priority.

Printf(" 0x2&0x2+0x4 -> %#x\n", 0x2&0x2+0x4) // &priority + //prints: 0x2&0x2+0x4 -> 0x6 //Go: (0x2 & 0x2) + 0x4 //C++: 0 x2 & (0 x2 + 0 x4) - > 0 x2 FMT. Printf (" 0 x2 + 0 x2 < < 0 x1 - > % # x \ n ", 0 x2 + 0 x2 / / < < < < 0 x1) priority + / / prints: 0x2 + 0x2 << 0x1 -> 0x6 //Go: 0x2 + (0x2 << 0x1) //C++: (0 x2 + 0 x2) < < 0 x1 - > 0 by 8 FMT) Printf (" 0 xf | 0 x2 ^ 0 x2 - > % # x \ n ", 0 xf | 0 x2 ^ 0 x2) ^ / / prints / / | priority: 0xf | 0x2 ^ 0x2 -> 0xd //Go: (0xf | 0x2) ^ 0x2 //C++: 0xf | (0x2 ^ 0x2) -> 0xf }Copy the code

Priority list:

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

Struct fields that are not exported cannot be encoded

The members of fields starting with lowercase letters cannot be accessed directly from the outside, so when struct performs encode operations in json, XML, GOB and other formats, these private fields will be ignored and get zero value when exported:

func main() { in := MyData{1, "two"} fmt.Printf("%#v\n", in) // main.MyData{One:1, two:"two"} encoded, Unmarshal(in) FMT.Println(string(encoded)) // {"One":1} &out) fmt.Printf("%#v\n", out) // main.MyData{One:1, two:""} }Copy the code

31. Goroutine is still running when the program exits

By default, the program does not exit until all goroutines have been executed. Note this:

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", Printf("[%v] is done\n", workerID)} workerID time.sleep (3 * time.second)Copy the code

The main() program exits without waiting for two goroutines to finish executing:

Common workaround: Use the “WaitGroup” variable, which causes the main program to wait for all goroutines to execute before exiting.

If your Goroutine is doing time-consuming things like looping messages, you can send them a kill message to shut them down. Or close a channel where they are both waiting to receive data:

Var wg sync.waitgroup done := make(chan struct{}) workerCount := 2 for I := 0; i < workerCount; i++ { wg.Add(1) go doIt(i, done, wg) } close(done) wg.Wait() fmt.Println("all done!" ) } func doIt(workerID int, done <-chan struct{}, wg sync.WaitGroup) { fmt.Printf("[%v] is running\n", workerID) defer wg.Done() <-done fmt.Printf("[%v] is done\n", workerID) }Copy the code

Execution Result:

It looks as if the goroutine is complete, but an error is reported:

fatal error: all goroutines are asleep – deadlock!

Why does a deadlock occur? Goroutine called WG.done () before exiting, the program should exit normally.

The reason for this is that the “WaitGroup” variable goroutine gets is a copy of the value of the var WG WaitGroup, which means that the doIt() parameter is only passed. So even if WG.done () is called in every goroutine, the WG variables in the main program are not affected.

Func main() {var wg sync.waitgroup done := make(chan struct{}) ch := make(chan interface{}) workerCount := 2 for i := 0; i < workerCount; I ++ {wg.add (1) go doIt(I, ch, done, &wg)} for I := 0; i < workerCount; I ++ {goroutine ch < -i} close(done) wg.wait () close(ch) FMT.Println("all done!" ) } func doIt(workerID int, ch <-chan interface{}, done <-chan struct{}, wg *sync.WaitGroup) { fmt.Printf("[%v] is running\n", workerID) defer wg.Done() for { select { case m := <-ch: fmt.Printf("[%v] m => %v\n", workerID, m) case <-done: fmt.Printf("[%v] is done\n", workerID) return } } }Copy the code

Operation effect:

32. Sends data to an unbuffered channel and returns as soon as the receiver is ready

The sender blocks only if the data is being processed by the Receiver. Depending on the running environment, the Receiver’s Goroutine may not have enough time to process the next data after the sender sends the data. Such as:

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"Copy the code

Operation effect:

33. Sending data to a closed channel can cause panic

Receiving data from a closed channel is safe:

The receive status value OK is false to indicate that there is no more data in the channel to receive. Similarly, if data is received from a buffered channel and no data is retrieved from the cached channel, the state value is also false

Sending data to a closed channel causes panic:

func main() { ch := make(chan int) for i := 0; i < 3; I ++ {go func(idx int) {ch < -idx}(I)} FMT.Println(<-ch) Sleep(2 * time.second) // Do something else}Copy the code

Running results:

For the buggy example above, a deprecated channel done can be used to tell the remaining Goroutines that they no longer need to send data to ch. < -done results in {} :

func main() {
	ch := make(chan int)
	done := make(chan struct{})

	for i := 0; i < 3; i++ {
		go func(idx int) {
			select {
			case ch <- (idx + 1) * 2:
				fmt.Println(idx, "Send result")
			case <-done:
				fmt.Println(idx, "Exiting")
			}
		}(i)
	}

	fmt.Println("Result: ", <-ch)
	close(done)
	time.Sleep(3 * time.Second)
}
Copy the code

Operation effect:

34. The value is usednilThe channel

Sending and receiving data on a channel with a value of nil will block permanently:

Func main() {var ch chan int // uninitialized, nil for I := 0; i < 3; i++ { go func(i int) { ch <- i }(i) } fmt.Println("Result: ", <-ch) time.Sleep(2 * time.Second) }Copy the code

Runtime deadlock error:

fatal error: all goroutines are asleep – deadlock!

goroutine 1 [chan receive (nil chan)]

Use this deadlock feature to dynamically open and close case blocks in select:

func main() { inCh := make(chan int) outCh := make(chan int) go func() { var in <-chan int = inCh var out chan<- int var  val int for { select { case out <- val: println("--------") out = nil in = inCh case val = <-in: println("++++++++++") out = outCh in = nil } } }() go func() { for r := range outCh { fmt.Println("Result: ", r) } }() time.Sleep(0) inCh <- 1 inCh <- 2 time.Sleep(3 * time.Second) }Copy the code

Operation effect:

34. If the function receiver transmits parameters in the value transmission mode, the original values of parameters cannot be modified

The parameters of a method receiver are similar to those of a normal function: if declared as a value, the method body gets a copy of the value of the parameter, and any modification to the parameter does not affect the original value.

The receiver is updated only if it is a map or slice variable and is used to update fields in map or elements in slice as Pointers:

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, Printf("num=%v key=%v items=%v\n", d.num, *d.key, Printf("num=%v key=%v items=%v\n", d.num, * d.keey, d.tems)}Copy the code

Running results:

Intermediate Chapter: 35-50

35. Close the HTTP response body

When you use the HTTP standard library to make a request and get a response, you need to manually close the response body, even if you don’t read any data from the response or the response is empty. It’s easy for novices to forget to turn it off manually, or write it in the wrong place:

// Panic func main() {resp, Err := http.get ("https://api.ipify.org?format=json") defer resp.body.close () // Resp may be nil and cannot read Body if err! = nil { fmt.Println(err) return } body, err := ioutil.ReadAll(resp.Body) checkError(err) fmt.Println(string(body)) } func checkError(err error) { if err ! = nil{ log.Fatalln(err) } }Copy the code

The above code can initiate the request correctly, but if the request fails, the variable resp is nil, causing panic:

panic: runtime error: invalid memory address or nil pointer dereference

You should check that the HTTP response error is nil before calling resp.body.close () to Close the response Body:

// Most cases correct example func main() {resp, Err := http.get ("https://api.ipify.org?format=json") checkError(err) defer resp.body.close () // The correct closing method for most cases Body, err := ioutil.ReadAll(resp.Body) checkError(err) fmt.Println(string(body)) }Copy the code

Output:

Get api.ipify.org?format=json: x509: certificate signed by unknown authority

In most request failures, resp is nil and ERR is non-nil. But if you get a redirect error, then both of them are non-nil, and you still end up with a memory leak. Two solutions:

  • You can close the non-nil response body directly in the code block that handles HTTP response errors.
  • Manual calldeferTo close the response body:
Func main() {resp, err := http.get ("http://www.baidu.com") // Close the correct position of the Body if resp! = nil { defer resp.Body.Close() } checkError(err) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) checkError(err) fmt.Println(string(body)) }Copy the code

An earlier implementation of resp.body.close () read the data from the response Body and discard it, ensuring that keep-Alive’s HTTP connection can be reused to handle more than one request. But the latest version of Go leaves the task of reading and discarding data to the user, and if you don’t handle it, the HTTP connection may be closed rather than reused, as documented in Go 1.5.

If your application reuses HTTP long connections heavily, you may want to add the following logic to the response handling code:

_, err = IO.Copy(ioutil.discard, resp.body) // Manually Discard the read dataCopy the code

If you need to read the response in its entirety, the above code needs to be written. For example, when decoding the API’s JSON response data:

json.NewDecoder(resp.Body).Decode(&data)
Copy the code

36. Close the HTTP connection

Some servers that support HTTP1.1 or HTTP1.0 with the Connection: keep-alive option will keep a long connection for a period of time. But the standard library “NET/HTTP” connection is only disconnected by default when the server asks to be closed, so your program may run out of socket descriptors. There are two solutions, after the request ends:

  • Set request variables directlyCloseThe field values fortrueAt the end of each request, the connection is actively closed.
  • Set Header request Header optionsConnection: close, and the response header returned by the server will have this option, at which point the HTTP library will actively disconnect.
Func main() {req, err := http.NewRequest("GET", "http://golang.org", Nil) checkError(err) req.Close = true // req.header. Add("Connection", "Close ") err := http.DefaultClient.Do(req) if resp ! = nil { defer resp.Body.Close() } checkError(err) body, err := ioutil.ReadAll(resp.Body) checkError(err) fmt.Println(string(body)) }Copy the code

You can create a custom configured HTTP transport client to disable HTTP global multiplexing:

func main() { tr := http.Transport{DisableKeepAlives: true} client := http.Client{Transport: &tr} resp, err := client.Get("https://golang.google.cn/") if resp ! = nil { defer resp.Body.Close() } checkError(err) fmt.Println(resp.StatusCode) // 200 body, err := ioutil.ReadAll(resp.Body) checkError(err) fmt.Println(len(string(body))) }Copy the code

Select scenarios based on requirements:

  • If your application makes a large number of requests to the same server, use the default hold long connection.
  • If your application connects to a large number of servers and each server requests only once or twice, close the connection when you receive the request. Or increase the maximum number of open filesfs.file-maxThe value of the.

37. Decode the numbers in JSON to interface type

When encode/decode JSON data, Go by default treats the value as float64. For example, the following code causes panic:

func main() { var data = []byte(`{"status": 200}`) var result map[string]interface{} if err := json.Unmarshal(data, &result); err ! = nil { log.Fatalln(err) } fmt.Printf("%T\n", Result ["status"]) // float64 var status = result["status"].(int)Copy the code

panic: interface conversion: interface {} is float64, not int

If you’re trying decode’s JSON field is an integer, you can:

  • Convert int values to float

  • Convert a float value required by decode to an int

Func main() {var data = []byte(' {"status": 200}`) var result map[string]interface{} if err := json.Unmarshal(data, &result); err ! = nil { log.Fatalln(err) } var status = uint64(result["status"].(float64)) fmt.Println("Status value: ", status) }Copy the code
  • useDecoderType to decode JSON data, specifying the value type of the field
Func main() {var data = []byte(' {"status": 200}`) var result map[string]interface{} var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() if err  := decoder.Decode(&result); err ! = nil { log.Fatalln(err) } var status, _ = result["status"].(json.Number).Int64() fmt.Println("Status value: ", status)} // You can use string to store numeric data, // Decode data as string func main() {var data = []byte({"status": 200}) var result map[string]interface{} var decoder = json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() if err := decoder.Decode(&result); err ! = nil { log.Fatalln(err) } var status uint64 err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status); checkError(err) fmt.Println("Status value: ", status) }Copy the code

– Use struct types to map the data you need into numeric types

Func main() {var data = []byte(' {"status": 200}`) var result struct { Status uint64 `json:"status"` } err := json.NewDecoder(bytes.NewReader(data)).Decode(&result)  checkError(err) fmt.Printf("Result: %+v", result) }Copy the code
  • You can use struct to map numeric types to json.rawMessage native data types

    This can be used if the JSON data is not in a hurry to decode or if the value type of a JSON field is not fixed.

// The state name can be either int or string, Func main() {records := [][]byte{[]byte(' {"status":200, "tag":"one"} '), []byte(`{"status":"ok", "tag":"two"}`), } for idx, record := range records { var result struct { StatusCode uint64 StatusName string Status json.RawMessage `json:"status"`  Tag string `json:"tag"` } err := json.NewDecoder(bytes.NewReader(record)).Decode(&result) checkError(err) var name string err = json.Unmarshal(result.Status, &name) if err == nil { result.StatusName = name } var code uint64 err = json.Unmarshal(result.Status, &code) if err == nil { result.StatusCode = code } fmt.Printf("[%v] result => %+v\n", idx, result) } }Copy the code

38. Comparison of struct, array, Slice and map values

The equality operator == can be used to compare structure variables, provided that the members of both structures are of comparable type:

type data struct {
	num     int
	fp      float32
	complex complex64
	str     string
	char    rune
	yes     bool
	events  <-chan string
	handler interface{}
	ref     *byte
	raw     [10]byte
}

func main() {
	v1 := data{}
	v2 := data{}
	fmt.Println("v1 == v2: ", v1 == v2)	// true
}
Copy the code

If any member of the two structures is not comparable, a compilation error will result. Note that array members are only comparable if the array elements are comparable.

Type data struct {num int checks [10]func() bool doIt func() bool M map[string]string // Bytes cannot be compared Func main() {v1 := data{} v2: = data{} fmt.Println("v1 == v2: ", v1 == v2)} func main() {v1 := data{} v2: = data{} fmt.Println("v1 == v2: ", v1 == v2)}Copy the code

invalid operation: v1 == v2 (struct containing [10]func() bool cannot be compared)

Go provides library functions to compare variables that cannot be compared using ==, such as DeepEqual() using the “reflect” package:

Func main() {v1 := data{} v2: = data{} fmt.println ("v1 == v2: ", reflect.DeepEqual(v1, v2)) // true m1 := map[string]string{"one": "a", "two": "b"} m2 := map[string]string{"two": "b", "one": "a"} fmt.Println("v1 == v2: ", reflect.deepequal (m1, m2)) // true s1 := []int{1, 2, 3} s2 := []int{1, 2, 3} Println("v1 == v2: ", reflect.deepequal (s1, s2)) // true}Copy the code

This comparison method may be slow, so use it according to your application requirements. DeepEqual() has other uses:

func main() {
	var b1 []byte = nil
	b2 := []byte{}
	fmt.Println("b1 == b2: ", reflect.DeepEqual(b1, b2))	// false
}
Copy the code

Note:

  • DeepEqual()It is not always appropriate to compare slices
func main() {
	var str = "one"
	var in interface{} = "one"
	fmt.Println("str == in: ", reflect.DeepEqual(str, in))	// true

	v1 := []string{"one", "two"}
	v2 := []string{"two", "one"}
	fmt.Println("v1 == v2: ", reflect.DeepEqual(v1, v2))	// false

	data := map[string]interface{}{
		"code":  200,
		"value": []string{"one", "two"},
	}
	encoded, _ := json.Marshal(data)
	var decoded map[string]interface{}
	json.Unmarshal(encoded, &decoded)
	fmt.Println("data == decoded: ", reflect.DeepEqual(data, decoded))	// false
}
Copy the code

If you want case-insensitive comparisons of English text in byte or string, you can use the ToUpper() and ToLower() functions of the bytes or Strings packages. Comparing bytes or strings in other languages should use bytes.equalfold () and strings.equalfold ()

Reflect.deepequal (), bytes.equal (), and bytes.pare () should not be used if byte slice contains data (ciphertext hash, token, etc.) to authenticate the user. This three functions easy to program timing attacks, when a “crypto/subtle” used in subtle. ConstantTimeCompare () function

  • reflect.DeepEqual()Empty slice is not considered equal to nil slice, but note thatbyte.Equal()Would consider them equal:
Func main() {var b1 []byte = nil b2: = []byte{} // b1 and b2 have the same length, the same byte order // nil and slice are the same in bytes fpt.println ("b1 == b2: ", bytes.Equal(b1, b2)) // true }Copy the code

39. Recover from Panic

Call recover() in a function deferred by defer, which catches/interrupts panic

Func main() {recover() // Nothing will catch panic("not good") // panic occurs, Println(" OK ")} // Correct recover call example func main() {defer func() {fmt.println ("recovered: recovered ") ", recover()) }() panic("not good") }Copy the code

As you can see from above, recover() is only called in the function that deferred executes.

Func main() {defer func() {doRecover()}() panic("not good")} func doRecover() {fmt.println ("recobered: ", recover()) }Copy the code

recobered: panic: not good

40. Update elements by updating references as range iterates through slice, array, and map

In the range iteration, the value obtained is actually a copy of the value of the element. Updating the copy does not change the original element, i.e. the address of the copy is not the address of the original element:

Func main() {data: = []int{1, 2, 3} for _, v := range data {v *= 10} FMT.Println("data: ", data) // data: [1 2 3] }Copy the code

If you want to change the value of an existing element, you should use the index to access it directly:

func main() { data := []int{1, 2, 3} for i, v := range data { data[i] = v * 10 } fmt.Println("data: ", data) // data: [10 20 30]}Copy the code

If your collection holds Pointers to values, make some changes. We still need to use the index to access the elements, but we can update the values directly with the elements from range:

func main() { data := []*struct{ num int }{{1}, {2}, {3},} for _, V: = range data {v.n um * = 10 / / direct use of pointer updating} FMT. Println (data [0], the data [1], the data [2]) / / & {10} and {20} and {30}}Copy the code

41. Data hidden in Slice

When a new slice is sliced from a slice, the new slice references the underlying array of the original slice. If this hole is skipped, the program may allocate a large number of temporary slices to point to parts of the original underlying array, resulting in unpredictable memory usage.

func get() []byte {
	raw := make([]byte, 10000)
	fmt.Println(len(raw), cap(raw), &raw[0])	// 10000 10000 0xc420080000
	return raw[:3]	// 重新分配容量为 10000 的 slice
}

func main() {
	data := get()
	fmt.Println(len(data), cap(data), &data[0])	// 3 10000 0xc420080000
}
Copy the code

This can be done by copying temporary Slice data instead of re-slicing:

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

func main() {
	data := get()
	fmt.Println(len(data), cap(data), &data[0])	// 3 3 0xc4200160b8
}
Copy the code

42. Misuse of data in Slice

As a simple example, rewrite the file path (stored in slice)

Split the path to point to each directory of different levels, modify the first directory name and reassemble the subdirectory name, create a new path:

Func main() {path := []byte("AAAA/BBBBBBBBB") sepIndex := bytes.indexbyte (path, '/') // 4 println(sepIndex) dir1 := path[:sepIndex] dir2 := path[sepIndex+1:] println("dir1: ", string(dir1)) // AAAA println("dir2: ", string(dir2)) // BBBBBBBBB dir1 = append(dir1, "suffix"...) println("current path: ", string(path)) // AAAAsuffixBBBB path = bytes.Join([][]byte{dir1, dir2}, []byte{'/'}) println("dir1: ", string(dir1)) // AAAAsuffix println("dir2: ", string(dir2)) // uffixBBBB println("new path: ", string(path)) // AAAAsuffix/uffixBBBBCopy the code

The result of the concatenation is not the correct AAAAsuffix/BBBBBBBBB, because the data referenced in both slice dir1 and dir2 is the underlying array of PATH. Line 13 changes the path of dir1 as well as the path of dir2

Solutions:

  • Reallocate the new slice and copy the data you need
  • Using the full slice expression:input[low:high:max], the capacity is set to max-low
// Use full slice expression func main() {path := []byte("AAAA/BBBBBBBBB") sepIndex := bytes.indexbyte (path, ' ') // 4 dir1 := path[:sepIndex:sepIndex] Dir2 := path[sepIndex+1:] dir1 = append(dir1, "suffix"...) path = bytes.Join([][]byte{dir1, dir2}, []byte{'/'}) println("dir1: ", string(dir1)) // AAAAsuffix println("dir2: ", string(dir2)) // BBBBBBBBB println("new path: ", string(path)) // AAAAsuffix/BBBBBBBBB }Copy the code

The third argument in line 6 is used to control the new capacity of dir1, and the new buffer will be allocated to hold the append excess element in dir1. Instead of overwriting the original path underlying array

43. The old slice

When you create a new slice from an existing slice, the data in both slices points to the same underlying array. If your program uses this feature, be aware of the stale slice problem.

In some cases, when an element is appended to a slice and the underlying array it points to is insufficient, a new array will be reallocated to store the data. Other slices point to the old underlying array.

Func main() {s1 := []int{1, 2, 3} FMT.Println(len(s1), cap(s1), s1) // 3 3 [1 2 3 ] s2 := s1[1:] fmt.Println(len(s2), cap(s2), Println(s1) // 22 [2 3] for I := range s2 {s2[I] += 20} Println(s2) // [22 23] s2 = append(s2, 4) For I := range s2 {s2[I] += 10} FMT.Println(s1) // [1 22 23] // FMT.Println(s2) //Copy the code

44. Type declarations and methods

Creating a new type from an existing non-interface type does not inherit the old method:

Type myMutex sync.mutex func main() {var MTX myMutex mtx.lock () mtx.unlock ()}Copy the code

Mtx. Lock undefined (type myMutex has no field or method Lock)…

If you need to use a method of the original type, you can embed the original type as an anonymous field in the new struct you define:

// Type myLocker struct {sync.mutex} func main() {var locker myLocker locke.lock () locke.unlock ()}Copy the code

The interface type declaration also preserves its set of methods:

type myLocker sync.Locker

func main() {
	var locker myLocker
	locker.Lock()
	locker.Unlock()
}
Copy the code

45. Jump out of for-switch and for-select code blocks

A break without a label breaks only the switch/ SELECT statement. If a return statement is not available, break the code block specified by the label:

Func main() {loop: for {switch {case true: fmt.println ("breaking out...") ) //break // always print breaking out... break loop } } fmt.Println("out..." )}Copy the code

Goto can also jump to the specified location, but it will still enter for-switch again, in an endless loop.

46. Iterating variables and closure functions in for statements

The iterated variables in the for statement are reused in each iteration, that is, the closure function created in the for statement always receives the same argument and gets the same iteration value when the Goroutine starts executing:

func main() { data := []string{"one", "two", "three"} for _, V := range data {go func() {fmt.println (v)}()} time.sleep (3 * time.second)Copy the code

The simplest solution: without modifying the Goroutine function, use local variables inside for to hold the iterated values and pass the parameters:

func main() { data := []string{"one", "two", "three"} for _, V := range data {vCopy := v go func() {fmt.println (vCopy)}()} time.sleep (3 * time.second) // one two three}Copy the code

Another workaround: pass the current iteration value directly to the anonymous function as a parameter:

func main() { data := []string{"one", "two", "three"} for _, V := range data {go func(in string) {fmt.println (in)}(v)} time.sleep (3 * time.second) // one two three}Copy the code

Note the three slightly more complex example differences below:

Type field struct {name string} func (p *field) print() {fmt.println (p.name)} func main() {data := []field{{"one"}, {"two"}, {"three"}} for _, V := range data {go v.int ()} time.sleep (3 * time.second) // []field{{"one"}, {"two"}, {"three"}} for _, Func main() {data := range data {v := v go v.string ()} time.sleep (3 * time.second [] * field {{" one "}, {" two "}, {" three "}} for _, v: = range data {/ / at this point iteration value v is the three elements of address, Go v.perint ()} time.sleep (3 * time.second) // one two three}Copy the code

47. Defer parameter values for the function

For functions that defer, arguments are evaluated at declaration time rather than execution time:

Func main() {var I = 1 defer fmt.println ("result: ", func() int {return I * 2}()) I ++}Copy the code

result: 2

48. The execution timing of the defer function

The function deferred to defer is executed at the end of the function calling it, not at the end of the block calling it, notice the distinction.

For example, in a long-running function that uses defer in the inner for loop to clean up resource calls from each iteration, there are problems:

Func main() {if len(os.args)! = 2 { os.Exit(1) } dir := os.Args[1] start, err := os.Stat(dir) if err ! = nil || ! start.IsDir() { os.Exit(2) } var targets []string filepath.Walk(dir, func(fPath string, fInfo os.FileInfo, err error) error { if err ! = nil { return err } if ! fInfo.Mode().IsRegular() { return nil } targets = append(targets, fPath) return nil }) for _, target := range targets { f, err := os.Open(target) if err ! = nil { fmt.Println("bad target:", target, "error:", Err) //error:too many open files break} defer f.close ()Copy the code

Create 10000 files first:

#! /bin/bash for n in {1.. 10000}; do echo content > "file${n}.txt" doneCopy the code

Operation effect:

The workaround: defer writes the delayed function to the anonymous function:

Func main() {//... for _, target := range targets { func() { f, err := os.Open(target) if err ! = nil { fmt.Println("bad target:", target, "error:", } defer f.close () // Close the file resource with f resource}()}}Copy the code

You can also remove defer and close it by calling f.close () when the file resource is finished.

49. Failed type assertion

In type assertion statements, failure to assert returns a “zero” value for the target type. Mixing an asserted variable with the original variable may cause exceptions:

Func main() {var data interface{} = "great" // data mix if data, ok := data.(int); ok { fmt.Println("[is an int], data: ", data) } else { fmt.Println("[not an int], data: ", data) // [isn't an int], data: 0}} // Correct example func main() {var data interface{} = "great" if res, ok := data. ok { fmt.Println("[is an int], data: ", res) } else { fmt.Println("[not an int], data: ", data) // [not an int], data: great } }Copy the code

50. Blocked Gorutinue with resource leaks

Rob Pike’s Go Concurrency Patterns talk at Google I/O 2012 discussed several basic Concurrency Patterns for Go, such as a function that takes the first data from a data set in complete code:

func First(query string, replicas []Search) Result {
	c := make(chan Result)
	replicaSearch := func(i int) { c <- replicas[i](query) }
	for i := range replicas {
		go replicaSearch(i)
	}
	return <-c
}
Copy the code

Each goroutine sends its results to the results channel, and the first data received by the channel is returned directly.

After the first data is returned, what happens to the other Goroutine search results? What about their own coroutines?

The resulting channel in First() is unbuffered, which means that only the First Goroutine can return, and since there is no receiver, the other Goroutines will block on sending. If you make a lot of calls, you may leak resources.

To avoid leaks, you should make sure that all goroutines exit correctly. There are two solutions:

  • Use a buffered channel to ensure that all of the goroutine returns are received:
func First(query string, replicas ... Search) Result { c := make(chan Result,len(replicas)) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c }Copy the code
  • Use the SELECT statement with the channel default statement that can hold a buffer value:

    Default’s buffered channel guarantees that the Goroutine will not block even if the resulting channel fails to receive data

func First(query string, replicas ... Search) Result { c := make(chan Result,1) searchReplica := func(i int) { select { case c <- replicas[i](query): default: } } for i := range replicas { go searchReplica(i) } return <-c }Copy the code
  • Use the special Cancellation channel to interrupt the execution of the remaining Goroutine:
func First(query string, replicas ... Search) Result { c := make(chan Result) done := make(chan struct{}) defer close(done) searchReplica := func(i int) { select { case c <- replicas[i](query): case <- done: } } for i := range replicas { go searchReplica(i) } return <-c }Copy the code

Rob Pike did not mention these problems in the presentation code to simplify the demonstration. But for novices, it may be used without thinking.

Advanced: 51-57

51. Use Pointers as receivers for methods

As long as the value is addressable, pointer methods can be called directly on the value. For a method, it is sufficient that its receiver is a pointer.

But not all values are addressable, such as elements of type map, variables referenced via interface:

type data struct { name string } type printer interface { print() } func (p *data) print() { fmt.Println("name: ", p.name)} func main() {d1 := data{"one"} d1.print() Var in printer = data{"two"} in.print() // type mismatch m := map[string]data{"x": Data {" three "}} m [r]. "x" print () / / m/" x "is not addressing / / changes frequently}Copy the code

cannot use data literal (type data) as type printer in assignment:

data does not implement printer (print method has pointer receiver)

Cannot call Pointer Method on m[” x “] cannot take the address of m[” x “]

52. Update the map field value

If a map field has a value of type struct, it cannot update a single field of that struct directly:

Type data struct {name string} func main() {m := map[string]data{"x": {"Tom"}, } m["x"].name = "Jerry" }Copy the code

Cannot assign to struct field m[” x “]. Name in map

Because elements in a map are not addressable. To be distinguished, slice elements are addressable:

type data struct {
	name string
}

func main() {
	s := []data{{"Tom"}}
	s[0].name = "Jerry"
	fmt.Println(s)	// [{Jerry}]
}
Copy the code

Note: Recently the GCCGO compiler was able to update the field values of map struct elements, but this was soon fixed. Officially, it is a potential feature of Go1.3, and does not need to be implemented in time. It is still in todo List.

There are two methods for updating the field values of struct elements in the map:

  • Using local variables
Type data struct {name string} func main() {m := map[string]data{"x": {"Tom"}, } r := m["x"] r.name = "Jerry" m["x"] = r fmt.Println(m) // map[x:{Jerry}] }Copy the code
  • Use map Pointers to elements
func main() { m := map[string]*data{ "x": "Tom" {}} m [r]. "x" name = "Jerry" directly modified / / m/" x "in the field FMT Println (" x") (m) / / & {Jerry}}Copy the code

But watch out for this misuse:

func main() {
	m := map[string]*data{
		"x": {"Tom"},
	}
	m["z"].name = "what???"	 
	fmt.Println(m["x"])
}
Copy the code

panic: runtime error: invalid memory address or nil pointer dereference

Nil interface and nil interface values

Although interface looks like a pointer type, it is not. A variable of type interface is nil only if both type and value are nil

If the value of your interface variable follows other variables (fog), be careful when comparing nil to equal:

func main() { var data *byte var in interface{} fmt.Println(data, data == nil) // <nil> true fmt.Println(in, In == nil) // <nil> true in = data ftt. Println(in, in == nil) // <nil> false // Data is nil, but in is not nil}Copy the code

If your function returns a value of type interface, beware of this trap:

Func main() {doIt := func(arg int) interface{} {var result *struct{} = nil if arg > 0 {result = &struct{}{}}  return result } if res := doIt(-1); res ! = nil { fmt.Println("Good result: ", res) // Good result: <nil> fmt.Printf("%T\n", res) // *struct {} Its value is nil fmt.printf ("%v\n", Func main() {doIt := func(arg int) interface{} {var result *struct{} = nil if arg > 0 { Result = &struct{}{}} else {return nil} return result} if res := doIt(-1); res ! = nil { fmt.Println("Good result: ", res) } else { fmt.Println("Bad result: ", res) // Bad result: <nil> } }Copy the code

54. Stack variables

You don’t always know whether your variables are assigned to the heap or stack.

In C++, variables created using new are always allocated to heap memory, but in Go, even if new() and make() are used to create variables, the allocation of memory for variables is still under the control of the Go compiler.

The Go compiler determines where to store a variable based on its size and the result of its “escape analysis”, so it returns the exact address of the local variable, which is not possible in C/C++.

When you add the -m parameter to go build or go run, you can accurately analyze the variable allocation position of the program:

Concurrency is a kind of Parallelism between Concurrency and GOMAXPROCS.

With Go 1.4 and below, only one execution context/OS thread is used, meaning that at most one Goroutine is executing at any one time.

The Go 1.5 version sets the number of executable contexts to Runtime.numcpu (), which is the number of logical CPU cores returned by the runtime.numcpu (). This can be adjusted using the GOMAXPROCS environment variable or dynamically using runtime.gomaxprocs ().

Misconception: GOMAXPROCS is the number of CPU cores that execute a Goroutine, see documentation

The value of GOMAXPROCS can exceed the actual number of cpus, up to 256 in 1.5

func main() { fmt.Println(runtime.GOMAXPROCS(-1)) // 4 fmt.Println(runtime.NumCPU()) // 4 runtime.GOMAXPROCS(20) Println(runtime.GOMAXPROCS(-1)) // 20 runtime.GOMAXPROCS(300) Println(runtime.GOMAXPROCS(-1)) // Go 1.9.2 // 300 }Copy the code

56. Reordering of read and write operations

Go may reorder the execution order of some operations, ensuring that operations are executed sequentially in a goroutine, but not in multiple Goroutines:

var _ = runtime.GOMAXPROCS(3) var a, B int func u1() {a = 1 b = 2} func u2() {a = 3 b = 4} func p() {println(a) println(b)} func main() {go u1() // multiple Go u2() go p() time.sleep (1 * time.second)}Copy the code

Operation effect:

If you want to keep multiple goroutines executed sequentially as in the code, you can use locking mechanisms in the channel or sync package, etc.

57. Priority scheduling

Your program may have a goroutine that blocks other goroutines at runtime, such as a for loop that does not allow the scheduler to run:

func main() { done := false go func() { done = true }() for ! done { } println("done !" )}Copy the code

The body of the for loop need not be empty, but if the code does not trigger the scheduler execution, you will have a problem.

The scheduler executes after GC, Go declaration, blocking channel, blocking system call, and lock operations, as well as on non-inline function calls:

func main() { done := false go func() { done = true }() for ! done { println("not done !" } println("done!" )}Copy the code

The -m argument can be added to parse inline functions called in the for block:

You can also start the scheduler manually using Gosched() in the Runtime package:

func main() { done := false go func() { done = true }() for ! done { runtime.Gosched() } println("done !" )}Copy the code

Operation effect:

conclusion

Thanks to the original author KCQon summary of this blog, let me benefit a lot.

Due to the translator’s limited level, it is inevitable that there will be misunderstandings, please point out in the comments section, thank you very much.

Follow up with translations of similar high quality articles 😍