当前位置:网站首页>66 pitfalls in go programming language: pitfalls and common errors of golang developers
66 pitfalls in go programming language: pitfalls and common errors of golang developers
2022-06-24 21:27:00 【Zen and the art of computer programming】

summary
Go Is a simple and interesting language , however , Like any other language , It also has some pitfalls …… Many of these traps are not entirely Go The fault of . If you come from another language , Some of these mistakes are natural traps . Others are due to wrong assumptions and lack of details .
beginner :
You can't put the left brace on a separate line
Unused variables
Unused imports
Short variable declarations can only be used inside functions
Redeclare variables with short variable declarations
You cannot use short variable declarations to set field values
Unexpected variable shadow
Out of commission “nil” To initialize a variable without an explicit type
Use “nil” Slicing and Mapping
Map capacity
String cannot be “nil”
Array function parameters
Slices and arrays “ Range ” Unexpected value in Clause
Slices and arrays are one-dimensional
Access to nonexistent mapping keys
Strings are immutable
Conversion between string and byte slice
String and index operators
Strings are not always UTF8 Text
String length
Multiline slice / Array / Missing comma in mapping literal
log.Fatal and log.Panic Not just logs
The built-in data structure operation is not synchronized
“ Range ” The iteration value of the string in the clause
Use “for range” Clause to traverse the map
“switch” Failure behavior in a statement
Increment and decrement
Bitwise non operator
Operator priority difference
The structure field not exported is not encoded
With activity Goroutines Your application exited
The target receiver returns to the unbuffered channel as soon as it is ready
Sending to closed channels can cause panic
Use “ zero ” passageway
A method with a value sink cannot change the original value
Intermediate beginner :
close HTTP Response Content
close HTTP Connect
JSON Encoder adds line breaks
JSON Special... In package escape keys and string values HTML character
take JSON Digital disaggregation is interface value
Hexadecimal or other non UTF8 Escape sequences cannot be used JSON A string value
Compare structure 、 Array 、 Slicing and Mapping
Recover from panic
Update and reference slices 、 Arrays and maps “for range” Item value in clause
In the slice “ hide ” data
Slicing data is corrupt
“ obsolete ” section
Type declarations and methods
break “for switch” and “for select” Code block
“for” Iteration variables and closures in statements
Delay function call parameter evaluation
Delay function call execution
Failed type assertion
Obstructed Goroutines And resource leakage
The same address of different zero size variables
iota The first use of does not always start from scratch
Advanced beginners :
Use the pointer receiver method on the value instance
to update map Value fields
“nil” Interface and “nil” Interface value
Stack and heap variables
GOMAXPROCS、 Concurrency and parallelism
Read and write operations are reordered
Preemptive scheduling
Cgo( Also known as a brave beginner ):
Import C And multiline import blocks
Import C and Cgo There is no blank line between comments
Cannot call with variable arguments C function
Pitfalls and common mistakes
1. You can't put the left brace on a separate line
Level : beginner
In most other languages that use braces , You can choose where to place them . Go is different . You can thank the automatic semicolon injection of this behavior ( No foresight ). Yes ,Go There are semicolons :-)
Failure :
package main
import "fmt"
func main()
{ //error, can't have the opening brace on a separate line
fmt.Println("hello there!")
}
Compile error :
/tmp/sandbox826898458/main.go:6: Grammar mistakes : Unexpected semicolon or line break {
The right way :
package main
import "fmt"
func main() {
fmt.Println("works!")
}
2. Unused variables
Level : beginner
If you have an unused variable , Your code will not compile . But there is one exception . You must use variables declared inside the function , But if you have unused global variables , It's fine too . It is also possible to have unused function parameters .
If you assign new values to unused variables , Your code will still not compile . You need to use variable values in some way to satisfy the compiler .
Failure :
package main
var gvar int //not an error
func main() {
var one int //error, unused variable
two := 2 //error, unused variable
var three int //error, even though it's assigned 3 on the next line
three = 3
func(unused string) {
fmt.Println("Unused arg. No compile error")
}("what?")
}
Compile error :
/tmp/sandbox473116179/main.go:6: A declared but unused /tmp/sandbox473116179/main.go:7: Two declared but not used /tmp/sandbox473116179/main.go:8: Three declared but not used
The right way :
package main
import "fmt"
func main() {
var one int
_ = one
two := 2
fmt.Println(two)
var three int
three = 3
one = three
var four int
four = four
}
Another option is to comment out or delete unused variables :-)
3. That is not used import
Level : beginner
If you are not using any export functions 、 Interface 、 Structure or variable , Your code will not compile .
If you really need imported packages , You can use a blank identifier _, As its package name , To avoid compilation failure . The blank identifier is used to import the side effects of the package .
Failure :
package main
import (
"fmt"
"log"
"time"
)
func main() {
}
Compile error :
/tmp/sandbox627475386/main.go:4: Imported but not used :“fmt”/tmp/sandbox627475386/main.go:5: Imported but not used :“log”/tmp/sandbox627475386/main.go:6: Import without use :“ Time ”
The right way :
package main
import (
_ "fmt"
"log"
"time"
)
var _ = log.Println
func main() {
_ = time.Now
}
Another option is to delete or comment out unused imports :-) The goimports Tools can help you .
4. Short variable declarations can only be used inside functions
Level : beginner
Failure :
package main
myvar := 1 //error
func main() {
}
Compile error :
/tmp/sandbox265716165/main.go:3: Undeclared statements outside the function
The right way :
package main
var myvar = 1
func main() {
}
5. Redeclare variables with short variable declarations
Level : beginner
You cannot redeclare variables in a standalone statement , But it is allowed in multivariable declarations that declare at least one new variable .
The redeclared variables must be in the same block , Otherwise you end up with a shadow variable .
Failure :
package main
func main() {
one := 0
one := 1 //error
}
Compile error :
/tmp/sandbox706333626/main.go:5: := No new variables on the left
The right way :
package main
func main() {
one := 0
one, two := 1,2
one,two = two,one
}
6. You cannot use short variable declarations to set field values
Level : beginner
Failure :
package main
import (
"fmt"
)
type info struct {
result int
}
func work() (int,error) {
return 13,nil
}
func main() {
var data info
data.result, err := work() //error
fmt.Printf("info: %+v\n",data)
}
Compile error :
prog.go:18: non-name data.result on left side of :=
Use temporary variables or declare all variables in advance and use standard assignment operators .
The right way :
package main
import (
"fmt"
)
type info struct {
result int
}
func work() (int,error) {
return 13,nil
}
func main() {
var data info
var err error
data.result, err = work() //ok
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("info: %+v\n",data) //prints: info: {result:13}
}
7. Variable coverage (Variable Shadowing)
Level : beginner
The short variable declaration syntax is very convenient ( Especially for languages that come from dynamic languages ), It's easy to think of it as a regular assignment operation . If you make this error in a new code block , There will be no compiler errors , But your application will not perform as you expect .
package main
import "fmt"
func main() {
x := 1
fmt.Println(x) //prints 1
{
fmt.Println(x) //prints 1
x := 2
fmt.Println(x) //prints 2
}
fmt.Println(x) //prints 1 (bad if you need 2)
}
Even for experienced Go For developers , This is also a very common trap . It's easy to make , And it's hard to find .
You can use the vet Command to find some of these problems . By default ,vet No shadow variable checks are performed . Make sure to use -shadow sign :go tool vet -shadow your_file.go
Please note that , The vet The command does not report all shadow variables . be used for go-nyet More active shadow variable detection .
8. Out of commission “nil” To initialize a variable without an explicit type
Level : beginner
“nil” Identifiers can be used as interfaces 、 function 、 The pointer 、 mapping 、 Slice and channel “ Zero value ”. If you do not specify a variable type , The compiler will not be able to compile your code , Because it can't guess the type .
Failure :
package main
func main() {
var x = nil //error
_ = x
}
Compile error :
/tmp/sandbox188239583/main.go:4: Use no type nil
The right way :
package main
func main() {
var x interface{} = nil
_ = x
}
9. Use “nil” Slicing and Mapping
Level : beginner
You can add elements to “nil” section , But yes map Doing the same will result in a runtime panic .
correct :
package main
func main() {
var s []int
s = append(s,1)
}
Failure :
package main
func main() {
var m map[string]int
m["one"] = 1 //error
}
10.map Capacity
Level : beginner
You can create map Specify capacity when , But you can't use cap(map) Function calculation capacity .
Failure :
package main
func main() {
m := make(map[string]int,99)
cap(m) //error
}
Compile error :
/tmp/sandbox326543983/main.go:5: cap Invalid parameter for m (type map[string]int)
11. String cannot be “nil”
Level : beginner
For those who are used to “nil” For developers who assign identifiers to string variables , This is a trap .
Failure :
package main
func main() {
var x string = nil //error
if x == nil { //error
x = "default"
}
}
Compile error :
/tmp/sandbox630560459/main.go:4: Cannot use in assignment nil As a type string /tmp/sandbox630560459/main.go:6: Invalid operation :x == nil( Mismatched type strings and nil)
The right way :
package main
func main() {
var x string //defaults to "" (zero value)
if x == "" {
x = "default"
}
}
12. Array function parameters : Array pointer type
Level : beginner
If you are C or C++ Developer , So your array is a pointer . When you pass an array to a function , Function references the same memory location , So they can update the original data .Go The array in is the value , So when you pass an array to a function , Function will get a copy of the original array data . If you try to update the array data , It could be a problem .
package main
import "fmt"
func main() {
x := [3]int{1,2,3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr) //prints [7 2 3]
}(x)
fmt.Println(x) //prints [1 2 3] (not ok if you need [7 2 3])
}
If you need to update the original array data , Please use the array pointer type .
package main
import "fmt"
func main() {
x := [3]int{1,2,3}
func(arr *[3]int) {
(*arr)[0] = 7
fmt.Println(arr) //prints &[7 2 3]
}(&x)
fmt.Println(x) //prints [7 2 3]
}
Another option is to use slicing . Even if your function gets a copy of the slice variable , It still references the original data .
package main
import "fmt"
func main() {
x := []int{1,2,3}
func(arr []int) {
arr[0] = 7
fmt.Println(arr) //prints [7 2 3]
}(x)
fmt.Println(x) //prints [7 2 3]
}
13. Slices and arrays “ Range ” Unexpected value in Clause
Level : beginner
If you are used to... In other languages “for-in” or “foreach” sentence , That's what happens .Go Medium “ Range ” Clauses are different . It generates two values : The first value is the item index , The second value is the project data .
Bad :
package main
import "fmt"
func main() {
x := []string{"a","b","c"}
for v := range x {
fmt.Println(v) //prints 0, 1, 2
}
}
well :
package main
import "fmt"
func main() {
x := []string{"a","b","c"}
for _, v := range x {
fmt.Println(v) //prints a, b, c
}
}
14. Slices and arrays are one-dimensional
Level : beginner
look Go Seems to support multidimensional arrays and slicing , But that's not the case . however , You can create arrays, arrays, or slices . For numeric applications that rely on dynamic multidimensional arrays , It is far from ideal in terms of performance and complexity .
You can use the original one-dimensional array 、“ Independent ” Sliced slices and “ Shared data ” Slice to build a dynamic multidimensional array .
If you use the original one-dimensional array , You need to be responsible for indexing when the array needs to grow 、 Boundary checking and memory reallocation .
Use “ Independent ” Slicing a slice to create a dynamic multidimensional array is a two-step process . First , You must create an external slice . then , You must assign each inner slice . The inner slices are independent of each other . You can expand and shrink other inner slices without affecting them .
package main
func main() {
x := 2
y := 4
table := make([][]int,x)
for i:= range table {
table[i] = make([]int,y)
}
}
Use “ Shared data ” Slicing to create a dynamic multidimensional array is a three-step process . First , You must create data that will save the original data “ Containers ” section . then , You create an external slice . Last , Initialize each internal slice by re slicing the original data slice .
package main
import "fmt"
func main() {
h, w := 2, 4
raw := make([]int,h*w)
for i := range raw {
raw[i] = i
}
fmt.Println(raw,&raw[4])
//prints: [0 1 2 3 4 5 6 7] <ptr_addr_x>
table := make([][]int,h)
for i:= range table {
table[i] = raw[i*w:i*w + w]
}
fmt.Println(table,&table[1][0])
//prints: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x>
}
There is a specification for multidimensional arrays and slices / Suggest , But at present, it seems to be a low priority function .
15. Access to nonexistent mapping keys
Level : beginner
For wanting to get “nil” For developers of identifiers , This is a trap ( Just like in other languages ). If the corresponding data type “ Zero value ” by “nil”, The return value is “nil”, But it is different for other data types . Check for proper “ Zero value ” Can be used to determine if a mapping record exists , But it's not always reliable ( for example , If you have a Boolean mapping , among “ Zero value ” For false , What would you do ). The most reliable way to know if a given map record exists is to check the second value returned by the map access operation .
Bad :
package main
import "fmt"
func main() {
x := map[string]string{"one":"a","two":"","three":"c"}
if v := x["two"]; v == "" { //incorrect
fmt.Println("no entry")
}
}
well :
package main
import "fmt"
func main() {
x := map[string]string{"one":"a","two":"","three":"c"}
if _,ok := x["two"]; !ok {
fmt.Println("no entry")
}
}
16. Strings are immutable
Level : beginner
Attempting to update a single character in a string variable with an index operator will result in a failure . A string is a read-only byte slice ( With some additional properties ). If you really need to update the string , If necessary, use byte slices instead of converting them to string types .
Failure :
package main
import "fmt"
func main() {
x := "text"
x[0] = 'T'
fmt.Println(x)
}
Compile error :
/tmp/sandbox305565531/main.go:7: Cannot be assigned to x[0]
The right way :
package main
import "fmt"
func main() {
x := "text"
xbytes := []byte(x)
xbytes[0] = 'T'
fmt.Println(string(xbytes)) //prints Text
}
Please note that , This is not actually the correct way to update characters in a text string , Because a given character can be stored in multiple bytes . If you really need to update the text string , Please convert it to Rune slice first . Even with Rune slicing , A single character can also span multiple runes , for example , If you have accented characters , That's what happens .“ character ” The complex and ambiguous nature of is Go The reason why a string is represented as a sequence of bytes .
17. Conversion between string and byte slice
Level : beginner
When you convert a string to a byte slice ( vice versa ) when , You will get a complete copy of the original data . It is unlike cast operations in other languages , Also unlike re slicing, the new slice variable points to the location of the same underlying array used by the original byte slice .
Go Indeed. []bytetostring and stringto[]byte The transformation has been optimized , To avoid additional allocations ( Yes todo The list is more optimized ).
When []byte Key is used to find map[string] Set , The first optimization avoids additional allocations :m[string(key)].
The second optimization avoids for range String conversion to []byte: Extra allocation in the clause of for i,v := range []byte(str) {...}.
18. String and index operators
Level : beginner
The index operator on the string returns a byte value , Not a character ( Just like in other languages ).
package main
import "fmt"
func main() {
x := "text"
fmt.Println(x[0]) //print 116
fmt.Printf("%T",x[0]) //prints uint8
}
If you need to access a specific string “ character ”(unicode Code points / Rune ), Please use the for range Clause . Official “unicode/utf8” Package and experimental utf8string package (golang.org/x/exp/utf8string) It's also very useful. .utf8string The bag contains a convenient At() Method . Converting a string to a piece of rune is also an option .
19. Strings are not always UTF8 Text
Level : beginner
The string value does not need to be UTF8 Text . They can contain any byte . The string is UTF8 The only time to use string literals . Even so , They can also contain other data using escape sequences .
Know if you have UTF8 Text string , Please use ValidString()“unicode/utf8” Functions in packages .
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data1 := "ABC"
fmt.Println(utf8.ValidString(data1)) //prints: true
data2 := "A\xfeC"
fmt.Println(utf8.ValidString(data2)) //prints: false
}
20. String length
Level : beginner
Suppose you are a python Developer , And you have the following code :
data = u''
print(len(data)) #prints: 1
When you convert it to something like Go Code snippets , You may be surprised .
package main
import "fmt"
func main() {
data := ""
fmt.Println(len(data)) //prints: 3
}
built-in len() Function returns the number of bytes , Not like it Python Medium unicode String returns the number of characters .
To be in Go The same results are obtained in , Please use RuneCountInString() “unicode/utf8” Functions in packages .
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data := ""
fmt.Println(utf8.RuneCountInString(data)) //prints: 1
Technically speaking , The RuneCountInString() Function does not return the number of characters , Because a single character can span multiple runes .
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
data := "é"
fmt.Println(len(data)) //prints: 3
fmt.Println(utf8.RuneCountInString(data)) //prints: 2
}
21. Multiline slice 、 Missing comma in array and mapping literal
Level : beginner
Failure :
package main
func main() {
x := []int{
1,
2 //error
}
_ = x
}
Compile error :
/tmp/sandbox367520156/main.go:6: Grammar mistakes : A comma is required before a line break in compound text /tmp/sandbox367520156/main.go:8: Undeclared statements outside the function /tmp/sandbox367520156/main.go:9 : Grammar mistakes : accident }
The right way :
package main
func main() {
x := []int{
1,
2,
}
x = x
y := []int{3,4,} //no error
y = y
}
If you leave a trailing comma when you collapse the declaration into one line , There will be no compiler errors .
22.log.Fatal and log.Panic Not just logs
Level : beginner
Logstores usually provide different logging levels .Fatal*() Unlike those logstores , If you call its and Panic*() function ,Go The log package in does more than just log . When your application calls these functions ,Go It will also terminate your application :-)
package main
import "log"
func main() {
log.Fatalln("Fatal Level: log entry") //app exits here
log.Println("Normal Level: log entry")
}
23. The built-in data structure operation is not synchronized
Level : beginner
Even though Go There are many native features that support concurrency , But concurrent secure data set consolidation is not one of them :-) It is your responsibility to ensure that data set updates are atomic .Goroutines and channels Is the recommended way to implement these atomic operations , But if it makes sense for your application , You can also use “sync” package .
24.“ Range ” The iteration value of the string in the clause
Level : beginner
Index value (“ Range ” The first value returned by the operation ) Is the current... Returned in the second value “ character ”(unicode Code points / Rune ) The index of the first byte of . It's not the current “ character ” The index of , Just like in other languages . Please note that , An actual character may be represented by multiple runes . If you need to use characters , Please be sure to check “ standard ” package (golang.org/x/text/unicode/norm).
With string variables for range Clause will attempt to interpret the data as UTF8 Text . For any byte sequence it doesn't understand , It will return 0xfffd Rune ( also called unicode Replace character ) Not the actual data . If you store any... In a string variable ( Not UTF8 Text ) data , Make sure you convert them into byte slices to get all stored data as is .
package main
import "fmt"
func main() {
data := "A\xfe\x02\xff\x04"
for _,v := range data {
fmt.Printf("%#x ",v)
}
//prints: 0x41 0xfffd 0x2 0xfffd 0x4 (not ok)
fmt.Println()
for _,v := range []byte(data) {
fmt.Printf("%#x ",v)
}
//prints: 0x41 0xfe 0x2 0xff 0x4 (good)
}
25. Use “for range” Clause traversal map
Level : beginner
If you want the items to be in a particular order ( for example , Key value sorting ), That's a problem . Each map iteration produces different results .Go The runtime attempts to Randomize the iteration sequence , But it doesn't always succeed , So you might get several identical Map iterations . See continuously 5 Don't be surprised at the same iteration .
package main
import "fmt"
func main() {
m := map[string]int{"one":1,"two":2,"three":3,"four":4}
for k,v := range m {
fmt.Println(k,v)
}
}
If you use Go Playground ( https://play.golang.org/ ), You will always get the same results , Because unless you make changes , Otherwise it won't recompile the code .
26.“switch” Failure behavior in a statement
Level : beginner
By default ,“switch” Statement “case” The block will break . This is different from other languages , The default behavior in other languages is to go to the next “case” block .
package main
import "fmt"
func main() {
isSpace := func(ch byte) bool {
switch(ch) {
case ' ': //error
case '\t':
return true
}
return false
}
fmt.Println(isSpace('\t')) //prints true (ok)
fmt.Println(isSpace(' ')) //prints false (not ok)
}
You can do this by “case” Use at end of block “fallthrough” Statement to force “case” Block pass . You can also rewrite your switch Statement to “case” Use the expression list in the block .
package main
import "fmt"
func main() {
isSpace := func(ch byte) bool {
switch(ch) {
case ' ', '\t':
return true
}
return false
}
fmt.Println(isSpace('\t')) //prints true (ok)
fmt.Println(isSpace(' ')) //prints true (ok)
}
27. Increment and decrement
Level : beginner
Many languages have increment and decrement operators . Different from other languages ,Go Prefix version of operation is not supported . Nor can you use these two operators in expressions .
Failure :
package main
import "fmt"
func main() {
data := []int{1,2,3}
i := 0
++i //error
fmt.Println(data[i++]) //error
}
Compile error :
/tmp/sandbox101231828/main.go:8: Grammar mistakes : accident ++ /tmp/sandbox101231828/main.go:9: Grammar mistakes : accident ++, expect :
The right way :
package main
import "fmt"
func main() {
data := []int{1,2,3}
i := 0
i++
fmt.Println(data[i])
}
28. Bitwise non operator
Level : beginner
Many languages use ~ One yuan NOT Operator ( Also known as bitwise complement ), but Go Reused XOR Operator ( ^).
Failure :
package main
import "fmt"
func main() {
fmt.Println(~2) //error
}
Compile error :
/tmp/sandbox965529189/main.go:6: The bitwise complement operator is ^
The right way :
package main
import "fmt"
func main() {
var d uint8 = 2
fmt.Printf("%08b\n",^d)
}
Go Still use ^XOR Operator , This may confuse some people .
If you like , You can NOT 0x02 In binary XOR operation ( for example ) To denote a dollar NOT operation ( for example 0x02 XOR 0xff). This can explain why ^ To be used again to denote a dollar NOT operation .
Go There's a special one “AND NOT” An operator ( &^), This increases NOT Confusion of operators . It looks like a A AND (NOT B) Special features that can be supported without parentheses /hack.
package main
import "fmt"
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))
}
29. Operator priority difference
Level : beginner
except “bit clear” The operator (&^) outside ,Go There is a standard set of operators , Many other languages share these operators . however , The precedence of operators is not always the same .
package main
import "fmt"
func main() {
fmt.Printf("0x2 & 0x2 + 0x4 -> %#x\n",0x2 & 0x2 + 0x4)
//prints: 0x2 & 0x2 + 0x4 -> 0x6
//Go: (0x2 & 0x2) + 0x4
//C++: 0x2 & (0x2 + 0x4) -> 0x2
fmt.Printf("0x2 + 0x2 << 0x1 -> %#x\n",0x2 + 0x2 << 0x1)
//prints: 0x2 + 0x2 << 0x1 -> 0x6
//Go: 0x2 + (0x2 << 0x1)
//C++: (0x2 + 0x2) << 0x1 -> 0x8
fmt.Printf("0xf | 0x2 ^ 0x2 -> %#x\n",0xf | 0x2 ^ 0x2)
//prints: 0xf | 0x2 ^ 0x2 -> 0xd
//Go: (0xf | 0x2) ^ 0x2
//C++: 0xf | (0x2 ^ 0x2) -> 0xf
}
30. The structure field not exported is not encoded
Level : beginner
Structural fields that begin with lowercase letters are not (json、xml、gob etc. ) code , So when you decode the structure , You will eventually get zero values in those fields that are not exported .
package main
import (
"fmt"
"encoding/json"
)
type MyData struct {
One int
two string
}
func main() {
in := MyData{1,"two"}
fmt.Printf("%#v\n",in) //prints main.MyData{One:1, two:"two"}
encoded,_ := json.Marshal(in)
fmt.Println(string(encoded)) //prints {"One":1}
var out MyData
json.Unmarshal(encoded,&out)
fmt.Printf("%#v\n",out) //prints main.MyData{One:1, two:""}
}
31. With activity Goroutines Your application exited
Level : beginner
The application will not wait for all of your goroutine complete . For the average beginner , This is a common mistake . Everyone starts somewhere , So it's not shameful to make a novice mistake :-)
package main
import (
"fmt"
"time"
)
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)
fmt.Printf("[%v] is done\n",workerId)
}
You'll see :
[0] Running
[1] Running
Complete !
One of the most common solutions is to use “WaitGroup” Variable . It will allow the master goroutine wait for , Until all the work goroutine complete . If your application has long-running staff with message processing loops , You also need a way to notify these goroutine It's time to quit . You can send... To each staff member “ Kill ” news . Another option is to turn off the channels that all workers are receiving . This is a time to all goroutine A simple way to signal .
package main
import (
"fmt"
"sync"
)
func main() {
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)
}
If you run this application , You'll see :
[0] Running
[0] Completed
[1] Running
[1] Completed
It seems that the staff are in the main goroutine Done before exiting . however ! You will also see :
Fatal error : all goroutine Are dormant - Deadlock !
That's not very good :-) What is going on? ? Why do deadlocks occur ? The workers left , They were executed wg.Done(). The application should work .
Life and death lock occurs because each staff member has obtained the original “WaitGroup” Copies of variables . When the staff performs wg.Done() when , Lord to it goroutine Medium “WaitGroup” Variables have no effect .
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
done := make(chan struct{})
wq := make(chan interface{})
workerCount := 2
for i := 0; i < workerCount; i++ {
wg.Add(1)
go doit(i,wq,done,&wg)
}
for i := 0; i < workerCount; i++ {
wq <- i
}
close(done)
wg.Wait()
fmt.Println("all done!")
}
func doit(workerId int, wq <-chan interface{},done <-chan struct{},wg *sync.WaitGroup) {
fmt.Printf("[%v] is running\n",workerId)
defer wg.Done()
for {
select {
case m := <- wq:
fmt.Printf("[%v] m => %v\n",workerId,m)
case <- done:
fmt.Printf("[%v] is done\n",workerId)
return
}
}
}
Now it works as expected :-)
32. The target receiver returns to the unbuffered channel as soon as it is ready
Level : beginner
Before the recipient processes your message , The sender will not be blocked . Depending on the machine on which you run the code , The receiver goroutine There may or may not be enough time to process the message before the sender continues .
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
for m := range ch {
fmt.Println("processed:",m)
}
}()
ch <- "cmd.1"
ch <- "cmd.2" //won't be processed
}
33. Sending to a closed channel will cause panic
Level : beginner
It is safe to receive from closed channels . Receive... In the statement ok The return value will be set to false Indicates that no data has been received . If you receive... From a buffered channel , You will first get the buffered data , Once it is empty ,ok The return value will be false.
Sending data to a closed channel can cause panic . This is a documented act , However, for new systems where the sending behavior may be expected to be similar to the receiving behavior Go For developers , This is not very intuitive .
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}
//get the first result
fmt.Println(<-ch)
close(ch) //not ok (you still have other senders)
//do other work
time.Sleep(2 * time.Second)
}
According to your application , The fix will be different . This could be a small code change , Or you may need to change your application design . Either way , You all need to make sure that your application does not attempt to send data to a closed channel .
Error examples can be fixed by using a special cancellation channel to signal the remaining staff that their results are no longer needed .
package main
import (
"fmt"
"time"
)
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,"sent result")
case <- done: fmt.Println(idx,"exiting")
}
}(i)
}
//get first result
fmt.Println("result:",<-ch)
close(done)
//do other work
time.Sleep(3 * time.Second)
}
34. Use “ zero ” passageway
Level : beginner
nil Always send and receive operations on channel blocks . This is a well documented act , But for the new Go It may be a surprise for developers .
package main
import (
"fmt"
"time"
)
func main() {
var ch chan int
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}
//get first result
fmt.Println("result:",<-ch)
//do other work
time.Sleep(2 * time.Second)
}
If you run code , You will see the following runtime errors :fatal error: all goroutines are asleep - deadlock!
This behavior can be used to dynamically enable and disable... In a statement case A way to block .select
package main
import "fmt"
import "time"
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:
out = nil
in = inch
case val = <- in:
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)
}
35. A method with a value sink cannot change the original value
Level : beginner
Method receivers are like regular function arguments . If it is declared as a value , So your function / Method will get a copy of your receiver parameters . This means that changes to the receiver do not affect the original value , Unless your receiver is a mapping or slicing variable , And the items in the collection you are updating or the fields you are updating in the receiver are pointers .
package main
import "fmt"
type data struct {
num int
key *string
items map[string]bool
}
func (this *data) pmethod() {
this.num = 7
}
func (this data) vmethod() {
this.num = 8
*this.key = "v.key"
this.items["vmethod"] = true
}
func main() {
key := "key.1"
d := data{1,&key,make(map[string]bool)}
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
//prints num=1 key=key.1 items=map[]
d.pmethod()
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
//prints num=7 key=key.1 items=map[]
d.vmethod()
fmt.Printf("num=%v key=%v items=%v\n",d.num,*d.key,d.items)
//prints num=7 key=v.key items=map[vmethod:true]
}
36. close HTTP Response Content
Grade : intermediate
When you use standards http When the library makes a request , You'll get a http Response variables . If you do not read the response body , You still need to close it . Please note that , You must also do this for an empty response . It's easy to forget , Especially for new Go Developer .
Some new Go The developer did try to close the response body , But they did the wrong thing .
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
defer resp.Body.Close()//not ok
if err != nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
This code applies to successful requests , But if http request was aborted ,resp The variable may be nil, This will lead to Runtime panic.
The most common reason to close the response body is defer stay http Call... After responding to error checking .
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close() //ok, most of the time :-)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
Most of the time , When your http When the request fails ,resp The variable will be . however , When you encounter redirection failure , Both variables will be . This means that you may still have a leak .nilerrnon-nilnon-nil
non-nil You can use the http A call to close the response body is added to the response error handling block to fix this leak . Another option is to use once defer Call to close the response body for all failed and successful requests .
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
original resp.Body.Close() In the implementation , The remaining response body data is also read and discarded . This ensures that if keepalive http Connection behavior , Then you can put http The connection is reused for another request . Abreast of the times http Client behavior is different . Now? , It is your responsibility to read and discard the remaining response data . If you don't ,http Connections may be closed rather than reused .
If you reuse http Connectivity is important to your application , You may need to add something like this at the end of the response processing logic :
_, err = io.Copy(ioutil.Discard, resp.Body)
If you do not read the entire response body immediately , It is necessary to do so , If you use the following code to handle json API Respond to , This may happen :
json.NewDecoder(resp.Body).Decode(&data)
37. close HTTP Connect
Grade : intermediate
some HTTP The server keeps the network connection open for a while ( be based on HTTP 1.1 Specifications and servers “ Keep active ” To configure ). By default , standard http The library is only on the target HTTP The network connection will not be closed until the server requests it . This means that your application may run out of sockets in some cases / File descriptor .
You can do this by Close The field in the request variable is set to To demand http The library closes the connection after the request completes true.
Another option is to add Connection Request the header and set it to close. The goal is HTTP The server should also use Connection: close Header response . When http When the library sees this response header , It also closes the connection .
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
req, err := http.NewRequest("GET","http://golang.org",nil)
if err != nil {
fmt.Println(err)
return
}
req.Close = true
//or do this:
//req.Header.Add("Connection", "close")
resp, err := http.DefaultClient.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(string(body)))
}
You can also disable... Globally http Connection reuse . You need to create a custom for it http Transport configuration .
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
tr := &http.Transport{DisableKeepAlives: true}
client := &http.Client{Transport: tr}
resp, err := client.Get("http://golang.org")
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp.StatusCode)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(string(body)))
}
If you go to the same HTTP The server sends a lot of requests , You can keep the network connection open . however , If your application has many different applications in a short time HTTP The server sends one or two requests , It is best to close the network connection as soon as your application receives a response . It may also be a good idea to increase the open file limit . however , The right solution depends on your application .
38.JSON Encoder adds line breaks
Grade : intermediate
When you find that the test failed because you didn't get the expected value , You are working for JSON Coding function writing test . What happened? ? If you are using JSON Encoder object , So you'll be coding JSON Get an extra line break at the end of the object .
package main
import (
"fmt"
"encoding/json"
"bytes"
)
func main() {
data := map[string]int{"key": 1}
var b bytes.Buffer
json.NewEncoder(&b).Encode(data)
raw,_ := json.Marshal(data)
if b.String() == string(raw) {
fmt.Println("same encoded data")
} else {
fmt.Printf("'%s' != '%s'\n",raw,b.String())
//prints:
//'{"key":1}' != '{"key":1}\n'
}
}
JSON Encoder Object is designed for streaming . Use JSON Streaming usually means... Separated by newline characters JSON object , This is it. Encode Method to add line breaks . This is a documented act , But often overlooked or forgotten .
39.JSON Special... In package escape keys and string values HTML character
Grade : intermediate
This is a documented act , But you must read all JSON Package documentation to understand it .SetEscapeHTML The method description discusses and、 Default encoding behavior for less than and greater than characters .
For a variety of reasons , This is a Go A very unfortunate design decision of the team . First , You can't do it for json.Marshal Call to disable this behavior . secondly , This is a poorly implemented security feature , Because it assumes that HTML Coding is sufficient to prevent all Web In the application XSS Loophole . There are many different contexts in which data can be used , Each context needs its own encoding method . Last , It's terrible , Because it assumes JSON The main use case for is web pages , By default, the configuration library and REST/HTTP API.
package main
import (
"fmt"
"encoding/json"
"bytes"
)
func main() {
data := "x < y"
raw,_ := json.Marshal(data)
fmt.Println(string(raw))
//prints: "x \u003c y" <- probably not what you expected
var b1 bytes.Buffer
json.NewEncoder(&b1).Encode(data)
fmt.Println(b1.String())
//prints: "x \u003c y" <- probably not what you expected
var b2 bytes.Buffer
enc := json.NewEncoder(&b2)
enc.SetEscapeHTML(false)
enc.Encode(data)
fmt.Println(b2.String())
//prints: "x < y" <- looks better
}
to Go Suggestions from the team …… Make it an option to join .
40. take JSON Digital disaggregation is interface value
Grade : intermediate
By default ,float64 When you will JSON Data decoding / When ungrouping into an interface ,Go take JSON The values in are treated as numbers . This means that the following code will result from panic Failure :
package main
import (
"encoding/json"
"fmt"
)
func main() {
var data = []byte(`{"status": 200}`)
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
fmt.Println("error:", err)
return
}
var status = result["status"].(int) //error
fmt.Println("status value:",status)
}
Runtime panic:
panic: Interface conversion : Interface is float64, instead of int
If you try to decode JSON The value is an integer , So you have multiple options .
Option one : Use floating point values as is :-)
Option 2 : Convert the floating-point value to the integer type you need .
package main
import (
"encoding/json"
"fmt"
)
func main() {
var data = []byte(`{"status": 200}`)
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
fmt.Println("error:", err)
return
}
var status = uint64(result["status"].(float64)) //ok
fmt.Println("status value:",status)
}
Option 3 : Use a type to ungroup JSON, And tell it to use the interface type Decoder To express JSON Numbers .Number
package main
import (
"encoding/json"
"bytes"
"fmt"
)
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 {
fmt.Println("error:", err)
return
}
var status,_ = result["status"].(json.Number).Int64() //ok
fmt.Println("status value:",status)
}
You can use Number The string representation of a value ungroups it into different numeric types :
package main
import (
"encoding/json"
"bytes"
"fmt"
)
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 {
fmt.Println("error:", err)
return
}
var status uint64
if err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status); err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("status value:",status)
}
Option four : Use struct Map your numeric values to the types of numeric types you need .
package main
import (
"encoding/json"
"bytes"
"fmt"
)
func main() {
var data = []byte(`{"status": 200}`)
var result struct {
Status uint64 `json:"status"`
}
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&result); err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("result => %+v",result)
//prints: result => {Status:200}
}
Option five : If you need delay value decoding , Please use struct Map your numeric values to of type a.json.RawMessage
If you have to execute a condition when the field type or structure may change JSON Field decoding , This option is useful .
package main
import (
"encoding/json"
"bytes"
"fmt"
)
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"`
}
if err := json.NewDecoder(bytes.NewReader(record)).Decode(&result); err != nil {
fmt.Println("error:", err)
return
}
var sstatus string
if err := json.Unmarshal(result.Status, &sstatus); err == nil {
result.StatusName = sstatus
}
var nstatus uint64
if err := json.Unmarshal(result.Status, &nstatus); err == nil {
result.StatusCode = nstatus
}
fmt.Printf("[%v] result => %+v\n",idx,result)
}
}
41. Hexadecimal or other non UTF8 Escape sequences cannot be used JSON A string value
Grade : intermediate
Go The expected string value is UTF8 code Of . This means your JSON String cannot have any hexadecimal escaped binary data ( And you must escape the backslash character ). This is indeed. Go inherited JSON trap , But it's in Go Often in applications , So mention it anyway .
package main
import (
"fmt"
"encoding/json"
)
type config struct {
Data string `json:"data"`
}
func main() {
raw := []byte(`{"data":"\xc2"}`)
var decoded config
if err := json.Unmarshal(raw, &decoded); err != nil {
fmt.Println(err)
//prints: invalid character 'x' in string escape code
}
}
If Go See a hexadecimal escape sequence ,Unmarshal/Decode The call will fail . If you really need to use backslashes in strings , Be sure to escape it with another backslash . If you want to use hexadecimal encoded binary data , You can escape backslashes , And then use JSON The decoded data in the string has its own hexadecimal escape .
package main
import (
"fmt"
"encoding/json"
)
type config struct {
Data string `json:"data"`
}
func main() {
raw := []byte(`{"data":"\\xc2"}`)
var decoded config
json.Unmarshal(raw, &decoded)
fmt.Printf("%#v",decoded) //prints: main.config{Data:"\\xc2"}
//todo: do your own hex escape decoding for decoded.Data
}
Another option is to JSON Object using byte arrays / Slice data type , But binary data must be base64 code .
package main
import (
"fmt"
"encoding/json"
)
type config struct {
Data []byte `json:"data"`
}
func main() {
raw := []byte(`{"data":"wg=="}`)
var decoded config
if err := json.Unmarshal(raw, &decoded); err != nil {
fmt.Println(err)
}
fmt.Printf("%#v",decoded) //prints: main.config{Data:[]uint8{0xc2}}
}
Other things to note are Unicode Replace character (U+FFFD).Go Replacement characters will be used instead of invalid UTF8, therefore Unmarshal/Decode The call will not fail , But the string value you get may not be what you expect .
42. Compare structure 、 Array 、 Slicing and Mapping
Grade : intermediate
== If each structure field can be compared with the equality operator , You can use the equality operator , To compare structural variables .
package main
import "fmt"
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) //prints: v1 == v2: true
}
If any of the structural fields are not comparable , Using the equality operator will result in a compile time error . Please note that , Only when their data items are comparable , Arrays are comparable .
package main
import "fmt"
type data struct {
num int //ok
checks [10]func() bool //not comparable
doit func() bool //not comparable
m map[string] string //not comparable
bytes []byte //not comparable
}
func main() {
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2:",v1 == v2)
}
Go Indeed, many helper functions are provided to compare variables that cannot be compared using comparison operators .
The most common solution is to use DeepEqual() Functions in reflection package .
package main
import (
"fmt"
"reflect"
)
type data struct {
num int //ok
checks [10]func() bool //not comparable
doit func() bool //not comparable
m map[string] string //not comparable
bytes []byte //not comparable
}
func main() {
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2)) //prints: v1 == v2: true
m1 := map[string]string{"one": "a","two": "b"}
m2 := map[string]string{"two": "b", "one": "a"}
fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2)) //prints: m1 == m2: true
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2)) //prints: s1 == s2: true
}
Except the speed is very slow ( This may or may not cause damage to your application ) outside ,DeepEqual() It also has its own problems .
package main
import (
"fmt"
"reflect"
)
func main() {
var b1 []byte = nil
b2 := []byte{}
fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) //prints: b1 == b2: false
}
DeepEqual() Empty slices are not considered equal to “nil” section . This behavior is consistent with your use of the bytes.Equal() Functions get different behavior .bytes.Equal() Think “nil” Is equal to an empty slice .
package main
import (
"fmt"
"bytes"
)
func main() {
var b1 []byte = nil
b2 := []byte{}
fmt.Println("b1 == b2:",bytes.Equal(b1, b2)) //prints: b1 == b2: true
}
DeepEqual() Comparative slices are not always perfect .
package main
import (
"fmt"
"reflect"
"encoding/json"
)
func main() {
var str string = "one"
var in interface{} = "one"
fmt.Println("str == in:",str == in,reflect.DeepEqual(str, in))
//prints: str == in: true true
v1 := []string{"one","two"}
v2 := []interface{}{"one","two"}
fmt.Println("v1 == v2:",reflect.DeepEqual(v1, v2))
//prints: v1 == v2: false (not ok)
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))
//prints: data == decoded: false (not ok)
}
If your byte slice ( Or a string ) Contains text data , When you need to be case insensitive ( In the use of 、 or Before ) When comparing values , You may want to use or come from “ byte ” and “ character string ”ToUpper() package . It applies to English texts , But not for text in many other languages . And should be used .ToLower()==bytes.Equal()bytes.Compare()strings.EqualFold()bytes.EqualFold()
If your byte slice contains secrets that need to be verified against user supplied data ( for example , Encrypt hash 、 Token, etc ), Please do not use reflect.DeepEqual(), bytes.Equal(), perhaps bytes.Compare() Because these functions make your application vulnerable to timing attacks . To avoid leaking timing information , Please use “crypto/subtle” Functions in packages ( for example ,subtle.ConstantTimeCompare()).
43. from panic To recover ( recover )
Grade : intermediate
The recover() Function can be used to capture / Intercept panic. call recover() Only in defer The delay function does not work until it is completed .
Incorrect :
package main
import "fmt"
func main() {
recover() //doesn't do anything
panic("not good")
recover() //won't be executed :)
fmt.Println("ok")
}
The right way :
package main
import "fmt"
func main() {
defer func() {
fmt.Println("recovered:",recover())
}()
panic("not good")
}
call recover() Only valid if called directly in your deferred function .
Failure :
package main
import "fmt"
func doRecover() {
fmt.Println("recovered =>",recover()) //prints: recovered => <nil>
}
func main() {
defer func() {
doRecover() //panic is not recovered
}()
panic("not good")
}
44. In the slice 、 Arrays and maps “ Range ” Clause to update and reference item values
Grade : intermediate
“ Range ” The data values generated in the clause are copies of the actual collection elements . They are not references to the original project . This means that updating the values does not change the original data . This also means that the address that gets the value does not provide you with a pointer to the original data .
package main
import "fmt"
func main() {
data := []int{1,2,3}
for _,v := range data {
v *= 10 //original item is not changed
}
fmt.Println("data:",data) //prints data: [1 2 3]
}
If you need to update the original set record values , Use the index operator to access the data .
package main
import "fmt"
func main() {
data := []int{1,2,3}
for i,_ := range data {
data[i] *= 10
}
fmt.Println("data:",data) //prints data: [10 20 30]
}
If your collection contains pointer values , The rules are slightly different . If you want the original record to point to another value , You still need to use the index operator , But you can use “for range” The second value in the clause updates the data stored in the target location .
package main
import "fmt"
func main() {
data := []*struct{num int} { {1},{2},{3}}
for _,v := range data {
v.num *= 10
}
fmt.Println(data[0],data[1],data[2]) //prints &{10} &{20} &{30}
}
45. In the slice “ hide ” data
Grade : intermediate
When slicing again , The new slice will An array referencing the original slice . If you forget this behavior , If your application allocates large temporary slices, create new slices from them to reference a small portion of the original data , May cause unexpected memory usage .
package main
import "fmt"
func get() []byte {
raw := make([]byte,10000)
fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x>
return raw[:3]
}
func main() {
data := get()
fmt.Println(len(data),cap(data),&data[0]) //prints: 3 10000 <byte_addr_x>
}
To avoid this trap , Please make sure Copy the required data from the temporary slice ( Instead of re slicing ).
package main
import "fmt"
func get() []byte {
raw := make([]byte,10000)
fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x>
res := make([]byte,3)
copy(res,raw[:3])
return res
}
func main() {
data := get()
fmt.Println(len(data),cap(data),&data[0]) //prints: 3 3 <byte_addr_y>
}
46. Slice data overlay
Grade : intermediate
Suppose you need to rewrite the path ( Stored in slices ). You re slice the path to refer to each directory that changes the first folder name , Then combine the names to create a new path .
package main
import (
"fmt"
"bytes"
)
func main() {
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path,'/')
dir1 := path[:sepIndex]
dir2 := path[sepIndex+1:]
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB
dir1 = append(dir1,"suffix"...)
path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB (not ok)
fmt.Println("new path =>",string(path))
}
It doesn't work as you expected . instead of “AAAAsuffix/BBBBBBBBB”, What you end up with is “AAAAsuffix/uffixBBBB”. This happens because both directory slices refer to the same underlying array data in the original path slice . This means that the original path has also been modified . According to your application , This may also be a problem .
This problem can be solved by allocating new slices and copying the data you need . Another option is to use the full slice expression .
package main
import (
"fmt"
"bytes"
)
func main() {
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path,'/')
dir1 := path[:sepIndex:sepIndex] //full slice expression
dir2 := path[sepIndex+1:]
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB
dir1 = append(dir1,"suffix"...)
path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB (ok now)
fmt.Println("new path =>",string(path))
}
Additional parameters in the full slice expression control the capacity of the new slice . Appending to this slice now triggers a new buffer allocation , Instead of overwriting the data in the second slice .
47. In the slice “ Old data ”
Grade : intermediate
Multiple slices can reference the same data . for example , When you create a new slice from an existing slice , This could happen . If your application relies on this behavior to function properly , Then you need to worry about “ obsolete ” section .
At some point , When the original array cannot hold more new data , Adding data to one of the slices will result in a new array allocation . Now the other slices will point to the old array ( With old data ).
import "fmt"
func main() {
s1 := []int{1,2,3}
fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3]
s2 := s1[1:]
fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3]
for i := range s2 { s2[i] += 20 }
//still referencing the same array
fmt.Println(s1) //prints [1 22 23]
fmt.Println(s2) //prints [22 23]
s2 = append(s2,4)
for i := range s2 { s2[i] += 10 }
//s1 is now "stale"
fmt.Println(s1) //prints [1 22 23]
fmt.Println(s2) //prints [32 33 14]
}
48. Type declarations and methods
Grade : intermediate
When you pass from existing ( Non interface ) When a type defines a new type to create a type declaration , You will not inherit methods defined for this existing type .
Failure :
package main
import "sync"
type myMutex sync.Mutex
func main() {
var mtx myMutex
mtx.Lock() //error
mtx.Unlock() //error
}
Compile error :
/tmp/sandbox106401185/main.go:9:mtx.Lock Undefined (myMutex Type has no field or method Lock) /tmp/sandbox106401185/main.go:10:mtx.Unlock Undefined (myMutex Type has no field or method Unlock)
If you really need methods of the original type , You can Define a new structure type , Embed the original type as an anonymous field .
The right way :
package main
import "sync"
type myLocker struct {
sync.Mutex
}
func main() {
var lock myLocker
lock.Lock() //ok
lock.Unlock() //ok
}
Interface type declarations also retain their method sets .
The right way :
package main
import "sync"
type myLocker sync.Locker
func main() {
var lock myLocker = new(sync.Mutex)
lock.Lock() //ok
lock.Unlock() //ok
}
49. from “for switch” and “for select” Block of code break
Grade : intermediate
No labels “break” Statements will only get you out of the inside switch/select block . If you use “return” Statement is not an option , So defining a tag for an external loop is the next best thing .
package main
import "fmt"
func main() {
loop:
for {
switch {
case true:
fmt.Println("breaking out...")
break loop
}
}
fmt.Println("out!")
}
“goto” Statements can also solve problems ......
50.“for” Iteration variables and closures in statements
Grade : intermediate
This is a Go The most common problem in English .for The iteration variables in the statement are reused in each iteration . This means that for Each closure created in the loop ( also called Function Literals ) Will reference the same variable ( And they will be in these goroutine Get the value of this variable at the beginning of execution ).
Incorrect :
package main
import (
"fmt"
"time"
)
func main() {
data := []string{"one","two","three"}
for _,v := range data {
go func() {
fmt.Println(v)
}()
}
time.Sleep(3 * time.Second)
//goroutines print: three, three, three
}
The simplest solution ( No need. goroutine Make any changes ) yes Save the current iteration variable value in for In a local variable within a loop block .
The right way :
package main
import (
"fmt"
"time"
)
func main() {
data := []string{"one","two","three"}
for _,v := range data {
vcopy := v // Local variables in the loop
go func() {
fmt.Println(vcopy)
}()
}
time.Sleep(3 * time.Second)
//goroutines print: one, two, three
}
Another solution is to pass the current iteration variable as a parameter to anonymous goroutine.
The right way :
package main
import (
"fmt"
"time"
)
func main() {
data := []string{"one","two","three"}
for _,v := range data {
go func(in string) {
fmt.Println(in)
}(v)
}
time.Sleep(3 * time.Second)
//goroutines print: one, two, three
}This is a slightly more complex version of the trap .
Incorrect :
package main
import (
"fmt"
"time"
)
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.print()
}
time.Sleep(3 * time.Second)
//goroutines print: three, three, three
}
The right way :
package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []field{ {"one"},{"two"},{"three"}}
for _,v := range data {
v := v
go v.print()
}
time.Sleep(3 * time.Second)
//goroutines print: one, two, three
}
What do you think you will see when you run this code ( Why? )?
package main
import (
"fmt"
"time"
)
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.print()
}
time.Sleep(3 * time.Second)
}
51. Delay function call parameter evaluation
Grade : intermediate
Deferred function call parameters are evaluated defer Statement ( Not when the function actually executes ). When you defer a method call , The same rules apply . Structure values are also stored with explicit method parameters and closed variables .
package main
import "fmt"
func main() {
var i int = 1
defer fmt.Println("result =>",func() int { return i * 2 }())
i++
//prints: result => 2 (not ok if you expected 4)
}
If you have pointer parameters , You can change the value they point to , Because in defer Only save... When evaluating statements The pointer .
package main
import (
"fmt"
)
func main() {
i := 1
defer func (in *int) { fmt.Println("result =>", *in) }(&i)
i = 2
//prints: result => 2
}
52. Delay function call execution
Grade : intermediate
Deferred calls are executed at the end of the containing function ( And in reverse order ), Not at the end of the containing code block . For new Go For developers , It is an easy mistake to confuse the deferred code execution rule with the variable scope rule . If you have a long - running function , It has one for Cyclic attempt defer Clean up resource calls in each iteration , This could be a problem .
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
if len(os.Args) != 2 {
os.Exit(-1)
}
start, err := os.Stat(os.Args[1])
if err != nil || !start.IsDir(){
os.Exit(-1)
}
var targets []string
filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if !fi.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) //prints error: too many open files
break
}
defer f.Close() //will not be closed at the end of this code block
//do something with the file...
}
}
One way to solve the problem is to wrap code blocks in functions .
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
if len(os.Args) != 2 {
os.Exit(-1)
}
start, err := os.Stat(os.Args[1])
if err != nil || !start.IsDir(){
os.Exit(-1)
}
var targets []string
filepath.Walk(os.Args[1], func(fpath string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if !fi.Mode().IsRegular() {
return nil
}
targets = append(targets,fpath)
return nil
})
for _,target := range targets {
func() {
f, err := os.Open(target)
if err != nil {
fmt.Println("bad target:",target,"error:",err)
return
}
defer f.Close() //ok
//do something with the file...
}()
}
}
Another option is to get rid of defer Statement :-)
53. Failed type assertion
Grade : intermediate
A failed type assertion returns the of the target type used in the assertion statement “ Zero value ”. When it blends with variable shadows , This can lead to unexpected behavior .
Incorrect :
package main
import "fmt"
func main() {
var data interface{} = "great"
if data, ok := data.(int); ok {
fmt.Println("[is an int] value =>",data)
} else {
fmt.Println("[not an int] value =>",data)
//prints: [not an int] value => 0 (not "great")
}
}
The right way :
package main
import "fmt"
func main() {
var data interface{} = "great"
if res, ok := data.(int); ok {
fmt.Println("[is an int] value =>",res)
} else {
fmt.Println("[not an int] value =>",data)
//prints: [not an int] value => great (as expected)
}
}
54. Obstructed Goroutines And resource leakage
Grade : intermediate
Rob Pike stay 2012 year Google I/O Upper “Go Concurrency Patterns” Some basic concurrency patterns were discussed in the lecture . Getting the first result from multiple goals is one of them .
func First(query string, replicas ...Search) Result {
c := make(chan Result)
searchReplica := func(i int) { c <- replicas[i](query) }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
This function starts a for each search copy goroutine. Every goroutine Send its search results to the results channel . Returns the first value of the result channel .
other goroutine What's the result of ?goroutines In itself ?
Result channel in function First() It's unbuffered . That means there's only one goroutine return . All the others goroutine Are trapped trying to send their results . This means that if you have multiple copies , Then each call will leak resources .
To avoid leakage , You need to make sure that all goroutine All quit . One possible solution is to use a buffer result channel large enough to hold all results .
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
}
Another possible solution is to use select Statements with cases default And a buffered result channel that can hold a value . The default The case ensures that even if the resulting channel cannot receive messages ,goroutines And it won't get stuck .
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
}
You can also use a special cancellation channel to interrupt staff .
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
}
Why does the presentation contain these errors ?Rob Pike Just don't want to complicate the slides . It makes sense , But for the new Go For developers , It could be a problem , They will use the code as is without considering that it may be problematic .
55. The same address of different zero size variables
Grade : intermediate
If you have two different variables , Shouldn't they have different addresses ? ok ,Go This is not the case :-) If you have a variable of zero size , They may share identical addresses in memory .
package main
import (
"fmt"
)
type data struct {
}
func main() {
a := &data{}
b := &data{}
if a == b {
fmt.Printf("same address - a=%p b=%p\n",a,b)
//prints: same address - a=0x1953e4 b=0x1953e4
}
}
56.iota The first use of does not always start from scratch
Grade : intermediate
Looks like the identifier iota Like an incremental operator . You start a new constant declaration , For the first time iota You get zero , The second time you use it, you get a , And so on . But that's not always the case .
package main
import (
"fmt"
)
const (
azero = iota
aone = iota
)
const (
info = "processing"
bzero = iota
bone = iota
)
func main() {
fmt.Println(azero,aone) //prints: 0 1
fmt.Println(bzero,bone) //prints: 1 2
}
iota It is indeed the index operator of the current row in the constant declaration block , So if you don't use it for the first time iota The first line in the constant declaration block , Then the initial value will not be zero .
57. Use... On value instances Pointer receiver Method
Grade : senior
As long as the value is addressable , You can call on the value Pointer receiver Method . let me put it another way , In some cases , You do not need the value receiver version of this method .
however , Not every variable is addressable . Map elements are not addressable . Variables referenced through interfaces are also not addressable .
package main
import "fmt"
type data struct {
name string
}
func (p *data) print() {
fmt.Println("name:",p.name)
}
type printer interface {
print()
}
func main() {
d1 := data{"one"}
d1.print() //ok
var in printer = data{"two"} //error
in.print()
m := map[string]data {"x":data{"three"}}
m["x"].print() //error
}
Compile error :
/tmp/sandbox017696142/main.go:21: Data literals cannot be used in assignments ( data type ) As a type printer : The data does not implement the printer ( The printing method has a pointer receiver )
/tmp/sandbox017696142/main.go:25: Cannot call m["x"] /tmp/sandbox017696142/main.go:25 Pointer method on : Can't get m["x"] The address of
58. to update map Value fields
Grade : senior
If you have a structure value mapping , You cannot update a single structure field .
Failure :
package main
type data struct {
name string
}
func main() {
m := map[string]data {"x":{"one"}}
m["x"].name = "two" //error
}
Compile error :
/tmp/sandbox380452744/main.go:9: Cannot be assigned to m["x"].name
It doesn't work , Because map elements are not addressable .
For new Go For developers , What's more puzzling is slice Elements are addressable .
package main
import "fmt"
type data struct {
name string
}
func main() {
s := []data { {"one"}}
s[0].name = "two" //ok
fmt.Println(s) //prints: [{two}]
}
Please note that , Not long ago, I could be in one of them Go compiler (gccgo) Update map element fields in , But the behavior was quickly fixed :-) It is also considered to be Go 1.3 The potential function of . It was not important enough to support it at that time , So it's still on the to-do list .
The first solution is to use temporary variables .
package main
import "fmt"
type data struct {
name string
}
func main() {
m := map[string]data {"x":{"one"}}
r := m["x"]
r.name = "two"
m["x"] = r
fmt.Printf("%v",m) //prints: map[x:{two}]
}
Another solution is to use Pointer mapping .
package main
import "fmt"
type data struct {
name string
}
func main() {
m := map[string]*data {"x":{"one"}}
m["x"].name = "two" //ok
fmt.Println(m["x"]) //prints: &{two}
}
By the way , What happens when you run this code ?
package main
type data struct {
name string
}
func main() {
m := map[string]*data {"x":{"one"}}
m["z"].name = "what?" //???
}
59.“nil” Interface and “nil” Interface value
Grade : senior
This is a Go The second most common problem in , because Interface is not a pointer , Even if they look like pointers . Only when their The type and value fields are “nil” when , The interface variable will be “nil”.
The interface type and value fields are populated based on the type and value of the variable used to create the corresponding interface variable . When you try to check whether the interface variable is equal to “nil” when , This can lead to unexpected behavior .
package main
import "fmt"
func main() {
var data *byte
var in interface{}
fmt.Println(data,data == nil) //prints: <nil> true
fmt.Println(in,in == nil) //prints: <nil> true
in = data
fmt.Println(in,in == nil) //prints: <nil> false
//'data' is 'nil', but 'in' is not 'nil'
}
When you have a function that returns an interface , Please pay attention to this trap .
Incorrect :
package main
import "fmt"
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) //prints: good result: <nil>
//'res' is not 'nil', but its value is 'nil'
}
}
The right way :
package main
import "fmt"
func main() {
doit := func(arg int) interface{} {
var result *struct{} = nil
if(arg > 0) {
result = &struct{}{}
} else {
return nil // return an explicit 'nil'
}
return result
}
if res := doit(-1); res != nil {
fmt.Println("good result:",res)
} else {
fmt.Println("bad result (res is nil)") //here as expected
}
}
60. Stack and heap variables
Grade : senior
You don't always know whether your variables are allocated on the stack or on the heap . stay C++ in , Use new Operator to create a variable always means that you have a Pile up Variable . stay Go in , Even using new() or make() function , The compiler also determines where to allocate variables . compiler According to the size of the variable and “ The analysis of Metonymy ” Result Choose where to store variables . This also means that you can return a reference to a local variable , This is in C or C++ And other languages .
If you need to know where the variable is assigned , Please put “-m”gc The sign is passed on to “go build” or “go run”( for example ,go run -gcflags -m app.go).
61.GOMAXPROCS、 Concurrency and parallelism
Grade : senior
Go 1.4 And below use only one execution context / Operating system threads . This means that only one... Can be executed at any given time goroutine. from 1.5 Start ,Go Set the number of execution contexts to runtime.NumCPU(). This number may be related to the logic on the system CPU The total number of cores matches , May not match , It depends on the process CPU Affinity settings . You can change GOMAXPROCS Environment variable or call runtime.GOMAXPROCS() Function to adjust this number .
There is a common misconception that GOMAXPROCS Express Go Will be used to run goroutine Of CPU Number .runtime.GOMAXPROCS() The functional documentation is even more confusing .GOMAXPROCS Variable description (https://golang.org/pkg/runtime/ ) Better at talking about OS threads .
You can set GOMAXPROCS To exceed CPU The number of . from 1.10 Start ,GOMAXPROCS There are no more restrictions . Once the maximum GOMAXPROCS yes 256, Later on 1.9 Added to 1024.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.GOMAXPROCS(-1)) //prints: X (1 on play.golang.org)
fmt.Println(runtime.NumCPU()) //prints: X (1 on play.golang.org)
runtime.GOMAXPROCS(20)
fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 20
runtime.GOMAXPROCS(300)
fmt.Println(runtime.GOMAXPROCS(-1)) //prints: 256
}
62. Read and write operations are reordered
Grade : senior
Go Some operations may be reordered , But it can ensure that it happens goroutine The overall behavior in does not change . however , It is not guaranteed to span multiple goroutine Execution order of .
package main
import (
"runtime"
"time"
)
var _ = runtime.GOMAXPROCS(3)
6
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()
go u2()
go p()
time.Sleep(1 * time.Second)
}
If you run this code multiple times , You may see these a and b Variable combinations :
1
2
3
4
0
2
0
0
1
4
a And the most interesting combination b yes “02”. Show b Previously updated a.
If you need to span multiple goroutine Keep the order of read and write operations , You need to use a channel or “ Sync ” Appropriate constructs in the package .
63. Preemptive scheduling
Grade : senior
There may be a rogue goroutine Will stop the others goroutine function . If you have one for A loop that does not allow the scheduler to run , This may happen .
package main
import "fmt"
func main() {
done := false
go func(){
done = true
}()
for !done {
}
fmt.Println("done!")
}
for The loop does not have to be empty . As long as it contains code that does not trigger the scheduler to execute , It will become a problem .
The scheduler will be in GC、“go” sentence 、 Blocking channel operation 、 Run after blocking system calls and locking operations . It can also run when calling non inline functions .
package main
import "fmt"
func main() {
done := false
go func(){
done = true
}()
for !done {
fmt.Println("not done!") //not inlined
}
fmt.Println("done!")
}
Make sure you are for Whether the function called in the loop is inline , Please put “-m”gc The sign is passed on to “go build” or “go run”( for example ,go build -gcflags -m).
Another option is to explicitly invoke the scheduler . You can use Gosched()“ Runtime ” Package to complete .
package main
import (
"fmt"
"runtime"
)
func main() {
done := false
go func(){
done = true
}()
for !done {
runtime.Gosched()
}
fmt.Println("done!")
}
Please note that , The code above contains the race condition . This is done deliberately to show the pitfalls of the problem .
64. Import C And multiline import blocks
Grade :CGO
You need to import “C” The bag can only be used Cgo. You can use one line import To do it , You can also use one import Block to do .
package main
/*
#include <stdlib.h>
*/
import (
"C"
)
import (
"unsafe"
)
func main() {
cs := C.CString("my go string")
C.free(unsafe.Pointer(cs))
}
If you use import Block format , You cannot import other packages in the same block .
package main
/*
#include <stdlib.h>
*/
import (
"C"
"unsafe"
)
func main() {
cs := C.CString("my go string")
C.free(unsafe.Pointer(cs))
}
Compile error :
./main.go:13:2: Undetermined C.free The name of
65.Import C and Cgo There is no blank line between comments
Grade :CGO
Cgo One of the first pitfalls of is import "C" Above statement cgo The position of the note .
package main
/*
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
)
func main() {
cs := C.CString("my go string")
C.free(unsafe.Pointer(cs))
}
Compile error :
./main.go:15:2: Undetermined C.free The name of
Make sure there are no blank lines above the declaration import "C".
66. Cannot call with variable arguments C function
Grade :CGO
You cannot directly call with variable arguments C function .
package main
/*
#include <stdio.h>
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
)
func main() {
cstr := C.CString("go")
C.printf("%s\n",cstr) //not ok
C.free(unsafe.Pointer(cstr))
}
Compile error :
./main.go:15:2: Unexpected type :...
You must set the variable parameter C The function is wrapped in a function with a known number of arguments .
package main
/*
#include <stdio.h>
#include <stdlib.h>
void out(char* in) {
printf("%s\n", in);
}
*/
import "C"
import (
"unsafe"
)
func main() {
cstr := C.CString("go")
C.out(cstr) //ok
C.free(unsafe.Pointer(cstr))
}
original text :http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html
边栏推荐
- Failed to open after installing Charles without any prompt
- Requests requests for web page garbled code resolution
- An example illustrates restful API
- 188. 买卖股票的最佳时机 IV
- Static routing experiment
- Auto. JS to automatically authorize screen capture permission
- Comprehensive comparison of the most popular packet capturing tools in the whole network
- Packaging_ Conversion between basic type and string type
- JMeter implementation specifies concurrent loop testing
- Different WordPress pages display different gadgets
猜你喜欢

Learn to use a new technology quickly

Nifi fast authentication configuration

Concepts of kubernetes components

JMeter basic learning records
![[cloud native learning notes] kubernetes Foundation](/img/c9/bd9ac90dd0dd27c450db33ad6b2fa5.jpg)
[cloud native learning notes] kubernetes Foundation

(to be optimized and modified) vivado DDR4 SDRAM (MIG) (2.2) IP core learning record

yeb_ Back first day

Rip/ospf protocol notes sorting

CondaValueError: The target prefix is the base prefix. Aborting.

ping: www.baidu.com: 未知的名称或服务
随机推荐
OSI and tcp/ip model
memcached全面剖析–2. 理解memcached的內存存儲
Does the developer want to change to software testing?
JMeter basic learning records
A/B测试助力游戏业务增长
Curl command
Typescript syntax
Variable setting in postman
Tutorial on obtaining JD cookies by mobile browser
浅谈MySql update会锁定哪些范围的数据
自己总结的wireshark抓包技巧
Basic database syntax learning
ping: www.baidu.com: 未知的名称或服务
What will you do if you have been ignored by your leaders at work?
PHP script calls command to get real-time output
Auto. JS to automatically authorize screen capture permission
Introduction to interval DP
Codeforces Round #720 (Div. 2)
Web automation: summary of special scenario processing methods
Network security review office starts network security review on HowNet