Understand the nil of the Go language

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

Recently saw a video on the tubing: Understanding Nil, very interesting, this article on the video to do a summary, code samples are from the video.

What's nil?

I believe that the programmer who wrote Golang is very familiar with the following code:

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

When there is a non nil -equal, it means that some error occurred, we need to do some processing of this error, and if it is equal to the instructions to nil run normally. Then what is it? nil Check the dictionary to see if it nil means nothing, or a value of 0. 0 value, zero value, isn't it a bit familiar? In the go language, if you declare a variable but do not assign it, the variable will have a default value of 0 for the type. This is the corresponding 0 value for each type:

bool      -> false                              numbers -> 0                                 string    -> ""      pointers -> nilslices -> nilmaps -> nilchannels -> nilfunctions -> nilinterfaces -> nil

As an example, when you define a struct:

type Person struct {  AgeYears int  Name string  Friends []Person}var p Person // Person{0, "", nil}

Variables are p declared but not assigned, so all fields of P have a corresponding value of 0. So, nil what exactly is this? In the Go document,Nil is a predefined identifier that represents the 0 value of a pointer, channel, function, interface, map, or slice , which is a predefined variable:

type Type intvar nil Type

Not a little surprised? nilnot one of the key words of go, you can even change nil the value yourself:

var nil = errors.New("hi")

This can be completely compiled, but it's best not to do it this way.

What's the use of nil?

After knowing what's going on nil , then say what's the nil use.

Pointers

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

The pointer represents the address that points to memory, which causes panic if the pointer to nil is dereferenced. So nil what's the use of pointers for? Let's look at an example of a binary tree calculation:

type tree struct {  v int  l *tree  r *tree}// first solutionfunc (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 code above has two problems, one is code duplication:

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

The other is when t it's nil time to panic:

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

How to solve the above problem? Let's take a 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 *personp.sayHi() // hi

For the method of pointer object, even if the value of the pointer is nil also can be called, based on this, we can just calculate the binary tree and the example of the transformation:

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

Is it a lot simpler to compare with the code just now? For nil pointers, it's OK to just judge the method in front of it, without repeating the judgment. Change to the value of the print binary tree or find a value for the binary tree is the same:

func(t *tree) String() string {  if t == nil {    return ""  }  return fmt.Sprint(t.l, t.v, t.r)}// nil receivers are useful: Findfunc (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's not very necessary, don't use NEWX () to initialize the values, but instead use their default values.

Slices

// nil slicesvar s []slicelen(s)  // 0cap(s)  // 0for range s  // iterates zero timess[i]  // panic: index out of range

A nil slice, in addition to not indexed, other operations are possible, when you need to fill the value of the function can be used append , slice will automatically expand. So nil what is the underlying structure of the slice? According to the official documentation, Slice has three elements, namely length, capacity, pointers to arrays:


Slice


When there are elements:


Slice


So we do not need to worry about the size of slice, use append words slice will automatically expand. (the video said slice automatic expansion speed quickly, do not worry about performance issues, this is debatable, in determining the size of slice only once memory allocation is always good)

Map

For go, Map,function,channel are special pointers, pointing to their specific implementations, which we can use for the time being.

// nil mapsvar m map[t]ulen(m)  // 0for range m // iterates zero timesv, ok := m[i] // zero(u), falsem[i] = x // panic: assignment to entry in nil map

For nil The map, we can simply think of it as a read-only map, unable to write operations, otherwise it will be panic. So nil what's the use of a map? Take a look at this example:

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}

For NewGet example, we need to pass in a parameter of type map, and this function just reads this parameter, we can pass in a non-null value:

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

or so:

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

But as I said earlier, the value of map 0 is nil , so when it's header empty, we can also pass it directly to a nil :

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

Isn't it a lot simpler? So, nil read the map as a read-only, empty map.

Channel

// nil channelsvar c chan t<- c      // blocks foreverc <- x    // blocks foreverclose(c)  // panic: close of nil channel

Closing a nil channel will cause the program panic (how to close the channel can see this article: How to gracefully close the go channel) For example, if there are now two channel responsible for input, a channel is responsible for the summary, Simple implementation Code:

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

If A or B is turned off in an external call, then the 0 is read continuously from a or B, which is not the same as what we want, we want to close A and B and then stop summarizing, modify the code:

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)}

When you know that the channel is closed, set the channel value to nil, which is equivalent to deactivating the Select Case clause because nil the channel is always blocked.

Interface

Interface is not a pointer, its underlying implementation consists of two parts, a type, a value, which is similar to: (Type, value). Only if the type and value are nil equal nil . Take a look at the following code:

func do() error {   // error(*doError, nil)  var err *doError  return err  // nil of type *doError}func main() {  err := do()  fmt.Println(err == nil)}

The output is the result false . The do function declares a *doErro variable and err then returns that the return value is an error interface, but this time the type has become: (*doerror,nil), so and nil certainly not equal. So when we write a function, don't declare the specific error variable, but should return directly nil :

func do() error {  return nil}

Take another look at this example:

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}

And here's the final output false . Why is it? Although the function returns a type, it returns a type, that is, it wrapDo error do *doError becomes (*doerror,nil), and the nature is nil not equal. Therefore, do not return the specific error type. Follow these two recommendations before you can use them with confidence if x != nil .

Summarize

After watching the video, I found that nil there are so many useful, it is really unexpected joy.
There is a lot of dry-filled video on the tubing, you can learn more.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.