What are the results of two nil comparisons

background

Hello, everyone. I'm asong. I saw a very interesting interview question in an exchange group a few days ago. Today I share it. Let's take a look at this question first:
fmt.Println(nil== nil)
What is the comparison result of two Nils?

true, false, or unable to compile? Let's think first and reveal the answer in the article.

Definition of nil in Go

In the official document of Go, nil is defined as follows:

// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type

Nil is a pre declared identifier representing pointer, channel, func, interface, map and slice. It can also be understood as follows: the zero value of pointer, channel, function, interface, map and slice is nil, just as the zero value of boolean type is false and the zero value of integer type is 0.

Deep understanding of nil

nil is not a keyword at all
Let's start with a piece of code:

func main()  {
 nil := "this is nil"
 fmt.Println(nil)
}
// Operation results
this is nil

How about this again?

func main()  {
 nil := "this is nil"
 fmt.Println(nil)
  var slice []string = nil
 fmt.Println(slice)
}
// Operation results
# command-line-arguments
./nil.go:10:6: cannot use nil (type string) as type []string in assignment

An error is reported directly when compiling. Because this nil is a string type, it is determined from here that nil is not a keyword in Go language. We can define the variable name as nil at will (but it is not recommended).

Default type of nil

Generally, pre declared identifiers have a default type. For example, the default type of itoa in Go language is int. what about the default type of nil? Let's write an example:

func main()  {
 const val1 = iota
 fmt.Printf("%T\n",val1)
 var val2 = nil
 fmt.Printf("%T\n",val2)
}
// Operation results
# command-line-arguments
./nil.go:10:6: use of untyped nil

An error has been reported at the time of compilation. The compiler tells us that typeless nil is used, so we can draw a conclusion:

Nil has no default type and its type is uncertain. When we use it, we must provide enough information to enable the compiler to infer the type expected by nil.

Comparison of nil

The comparison of nil can be divided into the following two cases:

  • Comparison of nil identifiers
  • Comparison of nil values

Let's first look at the comparison of nil identifiers, that is, the interview question at the beginning. Let's first look at the running results:

# command-line-arguments
./nil.go:8:18: invalid operation: nil == nil (operator == not defined on nil)

From the compilation results, we can see that the = = symbol is an undefined operation for nil, so it is impossible to compare two NILs.

Next, let's take a look at the value comparison of nil. Because nil has no type and is determined according to the context during compilation, to compare the value of nil is to compare different types of nil. This is divided into the comparison of nil values of the same type and the comparison of nil values of different types. Let's verify these two cases respectively.

  • Comparison of nil values of the same type
func main()  {
 // nil comparison of pointer types
 fmt.Println((*int64)(nil) == (*int64)(nil))
 // nil comparison of channel types
 fmt.Println((chan int)(nil) == (chan int)(nil))
 // nil comparison of func type
 fmt.Println((func())(nil) == (func())(nil)) // func() can only be compared with nil
 // nil comparison of interface type
 fmt.Println((interface{})(nil) == (interface{})(nil))
 // nil comparison of map type
 fmt.Println((map[string]int)(nil) == (map[string]int)(nil)) // map can only be compared with nil
 // nil comparison of slice type
 fmt.Println(([]int)(nil) == ([]int)(nil)) // slice can only be compared with nil
}

Operation results:

# command-line-arguments
./nil.go:13:28: invalid operation: (func())(nil) == (func())(nil) (func can only be compared to nil)
./nil.go:17:36: invalid operation: (map[string]int)(nil) == (map[string]int)(nil) (map can only be compared to nil)
./nil.go:19:27: invalid operation: ([]int)(nil) == ([]int)(nil) (slice can only be compared to nil)

From the running results, we can see that pointer type nil, channel type nil and interface type can be compared with each other, while func type, map type and slice type can only be compared with nil identifier. It is illegal to compare the two types with each other.

  • Comparison of different types of nil values
func main()  {
 var ptr *int64 = nil
 var cha chan int64 = nil
 var fun func() = nil
 var inter interface{} = nil
 var ma map[string]string = nil
 var slice []int64 = nil
 fmt.Println(ptr == cha)
 fmt.Println(ptr == fun)
 fmt.Println(ptr == inter)
 fmt.Println(ptr == ma)
 fmt.Println(ptr == slice)
 
 fmt.Println(cha == fun)
 fmt.Println(cha == inter)
 fmt.Println(cha == ma)
 fmt.Println(cha == slice)
 
 fmt.Println(fun == inter)
 fmt.Println(fun == ma)
 fmt.Println(fun == slice)
 
 fmt.Println(inter == ma)
 fmt.Println(inter == slice)
 
 fmt.Println(ma == slice)
}

Operation results:

# command-line-arguments
./nil.go:14:18: invalid operation: ptr == cha (mismatched types *int64 and chan int64)
./nil.go:15:18: invalid operation: ptr == fun (mismatched types *int64 and func())
./nil.go:17:18: invalid operation: ptr == ma (mismatched types *int64 and map[string]string)
./nil.go:18:18: invalid operation: ptr == slice (mismatched types *int64 and []int64)
./nil.go:20:18: invalid operation: cha == fun (mismatched types chan int64 and func())
./nil.go:22:18: invalid operation: cha == ma (mismatched types chan int64 and map[string]string)
./nil.go:23:18: invalid operation: cha == slice (mismatched types chan int64 and []int64)
./nil.go:25:18: invalid operation: fun == inter (operator == not defined on func)
./nil.go:26:18: invalid operation: fun == ma (mismatched types func() and map[string]string)
./nil.go:27:18: invalid operation: fun == slice (mismatched types func() and []int64)
./nil.go:27:18: too many errors

From the running results, we can conclude that only pointer type and channel type can be compared with interface type, and other types cannot be compared with each other. Why can pointer type and channel type be compared with interface type?

This answer is left blank first, because I didn't figure it out. Doesn't it mean that / any type implements the interface {} type? I don't understand here. I look forward to your answer.

Problems needing attention in using nil in different types

One point to pay attention to when comparing interface with nil

Let's start with an example:

func main()  {
 err := Todo()
 fmt.Println(err == nil)
}
 
type Err interface {
 
}
 
type err struct {
 Code int64
 Msg string
}
 
func Todo() Err  {
 var res *err
 return res
}
// Operation results
false

The output result is false. In Todo method, we declare a variable res, which is a pointer type, the zero value is nil, and the return value is the interface type. Logically, the return value interface type should also be nil, but the result is not the same. This is because we ignore a concept of interface type. Interface is not a simple value, but is divided into type and value. Therefore, the nil judgment of the interface will be true only when the type and value are both nil.

This is an easy problem for novices. We must pay attention to this problem.

Will panic occur when reading and writing data from a nil map

For this problem, let's just write an example to test it:

func main()  {
 var m map[string]string
 fmt.Println(m["asoong"])
 m["asong"] = "Golang DreamWorks"
}
// Operation results
panic: assignment to entry in nil map
 
goroutine 1 [running]:
main.main()
        go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:10 +0xed

According to the running results, we can see that a nil map can read data, but cannot write data, otherwise panic will occur. Therefore, if you want to use a map, you must use make for initialization.

Closing the channel of nil will cause panic

func main()  {
 var cha chan int
 close(cha)
}

Operation results:

panic: close of nil channel
 
goroutine 1 [running]:
main.main()
       /go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:5 +0x2a
 

According to the running results, we can conclude that closing a nil channel will lead to program panic. We should pay attention to this problem in use. There is another problem to pay attention to: reading and writing data in a nil channel will cause permanent blocking.

Precautions for using a slice for nil

func main()  {
 var slice []int64 = nil
 fmt.Println(len(slice))
 fmt.Println(cap(slice))
 for range slice{
 
 }
 fmt.Println(slice[0])
}
// Operation results
0
0
panic: runtime error: index out of range [0] with length 0
 
goroutine 1 [running]:
main.main()
        /go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:14 +0xf2
 

Based on this example, we can draw the following conclusions:

An index with nil cannot be indexed, otherwise panic will be triggered, and other operations are allowed.

Will panic be raised when the method receiver is nil

func main()  {
 var m *man
 fmt.Println(m.GetName())
}
 
 
type man struct {
 
}
 
func (m *man)GetName() string {
 return "asong"
}
// Operation results
asong

According to the running results, we can see that when the method receiver is nil, we can still access the corresponding method, but we should pay attention to the writing in the method, otherwise panic will also be caused. The above code is changed as follows:

func main()  {
 var m *man
 fmt.Println(m.GetName())
}
 
 
type man struct {
 Name string
}
 
func (m *man)GetName() string {
 return m.Name
}
// Operation results
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a6ec3]
 
goroutine 1 [running]:
main.(*man).GetName(...)
        go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:18
main.main()
        go/src/asong.cloud/Golang_Dream/code_demo/slice_demo/nil.go:9 +0x23

This is to directly trigger panic, so we need to do a pointer null judgment for the sake of program robustness.

A null pointer is a pointer that has no value

func main()  {
 var a = (*int64)(unsafe.Pointer(uintptr(0x0)))
 fmt.Println(a == nil)  //true
}
// Operation results
true

Here we do a small experiment with 0x0, which just proves that a null pointer is a pointer that does not point to any value.

summary

The article is drawing to a close. Let's reveal the answer to the beginning of the article. The knowledge points of nil comparison in the article can just answer this question. Nil identifier has no type, so = = is an undefined operation for nil and cannot be compared. This can be compared in python. In python, the two None values are always equal, Don't confuse your friends.

Finally, I suggest you take a look at this video: https://www.youtube.com/watch?v=ynoY2xz-F8s needs to climb over the wall. After reading this, you will have a new understanding of nil.

Keywords: Go

Added by toma42 on Wed, 02 Feb 2022 08:27:50 +0200