Three mistakes that golang beginners tend to make

  • 2020-06-15 09:17:03
  • OfStack

preface

It's been almost two months since I went from golang Xiaobai to golang engineer, and I'd like to share one of the most common mistakes new developers make. Some of these errors result in failure to compile, which is easy to spot, while others are not thrown at compile time, or even at run time, and without knowledge, you can scratch your head and not know where bug is.

1. Add data to nil map and nil slice

Please consider the following code is wrong, and then run 1 time:


package main

func main() {
 var m map[string]string
 m["name"] = "zzy"
}

If nothing else, this code will result in 1 panic:

[

panic: assignment to entry in nil map

]

This is because the code only declares the type of map, but does not create the underlying array for map. At this time, map does not actually exist in memory, namely nil map, which can run the following code for verification:


package main

import "fmt"

func main() {
 var m map[string]string
 if m == nil {
  fmt.Println("this map is a nil map")
 }
}

So if you want to use map successfully, you must use the built-in function make to create 1:


m := make(map[string]string)

It is also possible to use the literal method, as with make:


m := map[string]string{}

Similarly, adding data directly to nil slice is not allowed, because the bottom layer of slice is also an array, and the slice type is declared without the initialization of the make function, while the bottom array does not exist:


package main

func main() {
 var s []int
 s[0] = 1
}

The above code will generate 1 panic runtime error:index out of range, using the make function or literal:


package main

func main() {
 // The first 2 A parameter is slice the len . make slice Must be provided. You can also pass in a control 3 2 parameters as cap 
 s := make([]int, 1) 
 s[0] = 1
}

One might find that using the append function for nil slice without going through make is also valid:


package main

import "fmt"

func main() {
 var s []int
 s = append(s, 1)
 fmt.Println(s) // s => [1]
}

Because actually similar 1 struct slice itself, it has a len attribute, is the current length, there is also a cap attribute, is the length of the underlying array, append function will determine whether the incoming slice len and cap, if len is greater than cap, would call make function to generate a greater new array and the underlying array data copied (above all is I guess, without verification, interested students can go to challenge under 1 source), the process is similar to:


package main

import "fmt"

func main() {
 var s []int //len(s) and cap(s) Are all 0
 s = append(s, 1)
 fmt.Println(s) // s => [1]
}

func append(s []int, arg int) []int {
 newLen := len(s) + 1
 var newS []int
 if newLen > cap(s) {
  // Create a new slice , whose underlying array is more than twice as large 
  newS = make([]int, newLen, newLen*2)
  copy(newS, s)
 } else {
  newS = s[:newLen] // Just cut the original array 1 Under the line 
 }
 newS[len(s)] = arg
 return newS
}

The wrong use of nil map nil slice is not very terrible, after all, you can find out when compiling, the following is a very bad mistake, 1 accidentally hit, it is difficult to check.

2. Misuse := assignment causes variable overwrite

Take a look at this code and guess what it will print:


package main

import (
 "errors"
 "fmt"
)

func main() {
 i := 2
 if i > 1 {
  i, err := doDivision(i, 2)
  if err != nil {
   panic(err)
  }
  fmt.Println(i)
 }
 fmt.Println(i)
}

func doDivision(x, y int) (int, error) {
 if y == 0 {
  return 0, errors.New("input is invalid")
 }
 return x / y, nil
}

I guess one would say:

[

1
1

]

After the actual execution of 1 time, the result is:

[

1
2

]

Why is that? ?

This is because the golang the scope of the variable in the range of small to each of the lexical block (don't understand the classmate can be as simple as the part of {} package) is a separate scope, everyone know each internal scope statement will block outside the statement with the same name, and each if statement is a lexical chunks, that is, if in a if statement, accidentally use: = not = to a if statement outside the variable assignment, so will produce a new local variable, This is only valid after the assignment in the if statement. External variables with the same name will be masked and will not change anything because of the logic behind the assignment!

This may not be a mistake on a linguistic level, but in practice, if misused, bug can be very secretive. Such as in the example code, because before err is not declared, so use the: = assignment (graph save trouble, less wrote var err error), then neither the compilation times wrong, nor in the run times wrong, it will have you scratching your head, feel the logic clearly walk by the way, why the end result is always wrong, debugging until you 1 point 1 point, just discover oneself accidentally wrote: 1 more.

I have been pit several times because of this, every time looked up for a long time, thought it was their logic loophole, finally found that = was written :=, alas, said are tears.

Pass values as references

The difference between value type data and reference type data is something that I'm sure all of you can tell, otherwise you don't have to read it because you don't understand it.

In golang, array and struct are both value types, while slice, map and chan are reference types, so when we write code, we basically do not use array, but use slice instead. For struct, we try to use Pointers, so as to avoid the time and space consumption of copying data when passing variables, and avoid the situation that the original data cannot be modified.

If you don't understand this, the result can be faulty code or, more seriously, bug.

Consider this code and run 1 under:


package main

import "fmt"

type person struct {
 name string
 age byte
 isDead bool
}

func main() {
 p1 := person{name: "zzy", age: 100}
 p2 := person{name: "dj", age: 99}
 p3 := person{name: "px", age: 20}
 people := []person{p1, p2, p3}
 whoIsDead(people)
 for _, p := range people {
  if p.isDead {
   fmt.Println("who is dead?", p.name)
  }
 }
}

func whoIsDead(people []person) {
 for _, p := range people {
  if p.age < 50 {
   p.isDead = true
  }
 }
}

I'm sure many of you can see the problem at first glance, but some of you don't know the mechanics of for range grammar. golang in for range syntax is very convenient and can easily traverse array, slice, map structure, but it has a characteristic, is in the traversed traverse to the current element, copied to internal variable, the concrete is in whoIsDead function for range, will put each person people, copy to p this variable, similar to the operation:

[

p := person

]

As mentioned above, struct is a value type, so in the process of assigning value to p, one copy of person data actually needs to be generated to facilitate the internal use of for range.


package main

import "fmt"

func main() {
 var m map[string]string
 if m == nil {
  fmt.Println("this map is a nil map")
 }
}
0

So the operation p.isDead = true actually changes the newly generated p data, rather than the original person in people, where 1 bug is generated.

In the case of for range, where only data is read and no modification is required, it doesn't matter what you write. At best, the code is not perfect, and when the data needs to be modified, it is better to pass the struct pointer:


package main

import "fmt"

func main() {
 var m map[string]string
 if m == nil {
  fmt.Println("this map is a nil map")
 }
}
1

Run 1 below:

[

who is dead? px

]

everything is ok, great code.

There is another way, using the index to access person in people, changing the whoIsDead function to achieve the same purpose:


package main

import "fmt"

func main() {
 var m map[string]string
 if m == nil {
  fmt.Println("this map is a nil map")
 }
}
2

Ok, so that's the end of for range, let's move on to 1, which is the transfer and modification of the median value of map structure.

This code changes the previous people []person to map structure. Do you think there is any mistake? If so, where is the mistake?


package main

import "fmt"

type person struct {
 name string
 age byte
 isDead bool
}

func main() {
 p1 := person{name: "zzy", age: 100}
 p2 := person{name: "dj", age: 99}
 p3 := person{name: "px", age: 20}
 people := map[string]person{
  p1.name: p1,
  p2.name: p2,
  p3.name: p3,
 }
 whoIsDead(people)
 if p3.isDead {
  fmt.Println("who is dead?", p3.name)
 }
}

func whoIsDead(people map[string]person) {
 for name, _ := range people {
  if people[name].age < 50 {
   people[name].isDead = true
  }
 }
}

go run 1, error:

[

cannot assign to struct field people[name].isDead in map

]

This error is a bit confusing, I guess a lot of people don't understand. As the number of map elements increases, a larger array needs to be created to store the data. Then the previous address is invalid, because the data is copied to the new larger array, so the elements in map are neither addressable nor modifiable. This error means that you are not allowed to modify elements in map.

Even if the elements in map do not have these limitations, this code is still wrong. Think 1. Why? The answer has been said before.

So, how do you get it right? The old trick, again, is to use Pointers:


package main

import "fmt"

func main() {
 var m map[string]string
 if m == nil {
  fmt.Println("this map is a nil map")
 }
}
4

In addition, when attempting to modify the struct attribute directly in the interface{} assertion rather than by pointer:


package main

import "fmt"

func main() {
 var m map[string]string
 if m == nil {
  fmt.Println("this map is a nil map")
 }
}
5

1 compilation error will be reported directly:

[

cannot assign to p.(person).isDead

]

Even if the code is compiled correctly, always remember that struct is value type data and use Pointers to manipulate it. The correct way to do this is:


package main

import "fmt"

func main() {
 var m map[string]string
 if m == nil {
  fmt.Println("this map is a nil map")
 }
}
6

Finally, I have to say that golang pointer is really a necessary knowledge of home travel, promotion and salary increase, I hope students master it.

conclusion


Related articles: