Home  >  Article  >  Backend Development  >  What is nil in go language

What is nil in go language

青灯夜游
青灯夜游Original
2023-01-11 10:45:272976browse

nil is a predefined identifier in the Go language, representing a null or zero value. In the Go language, the zero value (initial value) of the Boolean type is false, the zero value of the numeric type is 0, the zero value of the string type is the empty string "''", and the pointer, slice, map, channel, function The zero value of the interface is nil. nil is not a keyword or reserved word. Pointers to nil of different types are the same. Nil of different types cannot be compared, and nil values ​​of the same type may not be compared.

What is nil in go language

The operating environment of this tutorial: Windows 7 system, GO version 1.18, Dell G3 computer.

What is nil

In the Go language, the zero value (initial value) of the Boolean type is false, the zero value of the numeric type is 0, and the zero value of the string type The zero value is the empty string "", and the zero value for pointers, slices, maps, channels, functions, and interfaces is nil.

nil is a predefined identifier in the Go language. Developers who have experience in developing other programming languages ​​may regard nil as null (NULL) in other languages. In fact, this is not completely correct. Yes, because there are many differences between nil in Go language and null in other languages.

Go language nil feature

  • nil identifiers cannot be compared

  • nil is not critical Words or reserved words

  • The pointers of different types of nil are the same

  • The pointers of different types of nil cannot be compared

  • Two nil values ​​of the same type may not be compared

  • nil is the zero value of a common reference type

I believe it Programmers who have written Golang are very familiar with the following piece of code:

if err != nil {
    // do something....
}

When it is not equal to nil, it means that some error has occurred, and we need to correct this error Perform some processing, and if it is equal to nil, it means the operation is normal. So what is nil? Check the dictionary and you will know that nil means nothing, or a zero value. Zero value, zero value, does it sound familiar? In the Go language, if you declare a variable but do not assign a value to it, the variable will have a default value of zero. This is the corresponding zero value for each type:

bool      -> false                              
numbers -> 0                                 
string    -> ""      

pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil

For example, when you define a struct:

type Person struct {
  AgeYears int
  Name string
  Friends []Person
}

var p Person // Person{0, "", nil}

The variablep is only declared but not assigned, so All fields of p have corresponding zero values. So, what exactly is this nil? Go's documentation says that nil is a predefined identifier that represents the zero value of a pointer, channel, function, interface, mapping or slice , which is a predefined variable:

type Type int
var nil Type

Are you a little surprised? nil is not one of the keywords of Go. You can even change the value of nil yourself:

var nil = errors.New("hi")

This is completely compilable, but in the end It's better not to do it like this.

What is the use of nil

After understanding what nil is, let’s talk about the use of nil.

pointers

var p *int
p == nil    // true
*p          // panic: invalid memory address or nil pointer dereference

The pointer represents the address pointing to the memory. If a nil pointer is dereferenced, panic will occur. So what is the use of a pointer that is nil? Let’s first look at an example of calculating the sum of binary trees:

type tree struct {
  v int
  l *tree
  r *tree
}

// first solution
func (t *tree) Sum() int {
  sum := t.v
  if t.l != nil {
    sum += t.l.Sum()
  }
  if t.r != nil {
    sum += t.r.Sum()
  }
  return sum
}

The above code has two problems, one is code duplication:

if v != nil {
  v.m()
}

and the other is when t is nil will panic:

var t *tree
sum := t.Sum()   // panic: invalid memory address or nil pointer dereference

How to solve the above problem? Let's first look at an example of a pointer receiver:

type person struct {}
func sayHi(p *person) { fmt.Println("hi") }
func (p *person) sayHi() { fmt.Println("hi") }
var p *person
p.sayHi() // hi

For the method of the pointer object, it can be called even if the value of the pointer is nil. Based on this, we can Let’s modify the example of calculating the sum of binary trees just now:

func(t *tree) Sum() int {
  if t == nil {
    return 0
  }
  return t.v + t.l.Sum() + t.r.Sum()
}

Is it much simpler compared to the code just now? For nil pointers, you only need to make a judgment before the method and it will be ok. There is no need to make repeated judgments. It is the same when printing the value of a binary tree or searching for a certain value in a binary tree:

func(t *tree) String() string {
  if t == nil {
    return ""
  }
  return fmt.Sprint(t.l, t.v, t.r)
}

// nil receivers are useful: Find
func (t *tree) Find(v int) bool {
  if t == nil {
    return false
  }
  return t.v == v || t.l.Find(v) || t.r.Find(v)
}

So if it is not necessary, do not use NewX() to initialize the values, but use their default values.

slices

// nil slices
var s []slice
len(s)  // 0
cap(s)  // 0
for range s  // iterates zero times
s[i]  // panic: index out of range

A slice of nil, except that it cannot be indexed, other operations are possible , when you need to fill in the value, you can use the append function, and the slice will automatically expand. So what is the underlying structure of the slice that is nil? According to the official documentation, slice has three elements, namely length, capacity, and pointer to the array:

What is nil in go language

When there are elements:

What is nil in go language

所以我们并不需要担心slice的大小,使用append的话slice会自动扩容。(视频中说slice自动扩容速度很快,不必担心性能问题,这个值得商榷,在确定slice大小的情况只进行一次内存分配总是好的)

map

对于Go来说,map,function,channel都是特殊的指针,指向各自特定的实现,这个我们暂时可以不用管。

// nil maps
var m map[t]u
len(m)  // 0
for range m // iterates zero times
v, ok := m[i] // zero(u), false
m[i] = x // panic: assignment to entry in nil map

对于nil的map,我们可以简单把它看成是一个只读的map,不能进行写操作,否则就会panic。那么nil的map有什么用呢?看一下这个例子:

func NewGet(url string, headers map[string]string) (*http.Request, error) {
  req, err := http.NewRequest(http.MethodGet, url, nil)
  if err != nil {
    return nil, err
  }

  for k, v := range headers {
    req.Header.Set(k, v)
  }
  return req, nil
}

对于NewGet来说,我们需要传入一个类型为map的参数,并且这个函数只是对这个参数进行读取,我们可以传入一个非空的值:

NewGet("http://google.com", map[string]string{
  "USER_AGENT": "golang/gopher",
},)

或者这样传:

NewGet("http://google.com", map[string]string{})

但是前面也说了,map的零值是nil,所以当header为空的时候,我们也可以直接传入一个nil

NewGet("http://google.com", nil)

是不是简洁很多?所以,把nil map作为一个只读的空的map进行读取吧。

channel

// nil channels
var c chan t
<- c      // blocks forever
c <- x    // blocks forever
close(c)  // panic: close of nil channel

关闭一个nil的channel会导致程序panic(如何关闭channel可以看这篇文章:如何优雅地关闭Go channel)举个例子,假如现在有两个channel负责输入,一个channel负责汇总,简单的实现代码:

func merge(out chan<- int, a, b <-chan int) {
  for {
    select {
      case v := <-a:
        out <- v
      case v := <- b:
        out <- v
    }
  }
}

如果在外部调用中关闭了a或者b,那么就会不断地从a或者b中读出0,这和我们想要的不一样,我们想关闭a和b后就停止汇总了,修改一下代码:

func merge(out chan<- int, a, b <-chan int) {
  for a != nil || b != nil {
    select {
      case v, ok := <-a:
          if !ok {
            a = nil
            fmt.Println("a is nil")
            continue
          }
          out <- v
      case v, ok := <-b:
          if !ok {
            b = nil
            fmt.Println("b is nil")
            continue
          }
          out <- v
    }
  }
  fmt.Println("close out")
  close(out)
}

在知道channel关闭后,将channel的值设为nil,这样子就相当于将这个select case子句停用了,因为nil的channel是永远阻塞的。

interface

interface并不是一个指针,它的底层实现由两部分组成,一个是类型,一个值,也就是类似于:(Type, Value)。只有当类型和值都是nil的时候,才等于nil。看看下面的代码:

func do() error {   // error(*doError, nil)
  var err *doError
  return err  // nil of type *doError
}

func main() {
  err := do()
  fmt.Println(err == nil)
}

输出结果是falsedo函数声明了一个*doErro的变量err,然后返回,返回值是error接口,但是这个时候的Type已经变成了:(*doError,nil),所以和nil肯定是不会相等的。所以我们在写函数的时候,不要声明具体的error变量,而是应该直接返回nil

func do() error {
  return nil
}

再来看看这个例子:

func do() *doError {  // nil of type *doError
  return nil
}

func wrapDo() error { // error (*doError, nil)
  return do()       // nil of type *doError
}

func main() {
  err := wrapDo()   // error  (*doError, nil)
  fmt.Println(err == nil) // false
}

这里最终的输出结果也是false。为什么呢?尽管wrapDo函数返回的是error类型,但是do返回的却是*doError类型,也就是变成了(*doError,nil),自然也就和nil不相等了。因此,不要返回具体的错误类型。遵从这两条建议,才可以放心地使用if x != nil

总结

看完了那个视频,发现nil还有这么多用处,真是意外之喜。
油管上面还有很多干货满满的视频,可以多学习学习咯。

【相关推荐:Go视频教程编程教学

The above is the detailed content of What is nil in go language. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn