What is nil?

Nil is not a keyword

There are 25 keywords in Golang, and nil is not included.

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
Copy the code

The word nil is arbitrary in the go program. Try this code:

func main()  {
   nil := "1"
   fmt.Println(nil)
   var slice []string = nil
   fmt.Println(slice)
}
Copy the code

1.2, is a variable, is a pre-declared identifier

// nil is a predeclared identifier representing the zero value for a // pointer, channel, func, interface, map, or slice type. // Type must be a pointer, channel, func, interface, map, or slice type. var nil Type // Type is here for the purposes of documentation only. It is a stand-in // for any Go type,  but represents the same type for any given function // invocation. type Type intCopy the code

From the source code, nil is just a variable of Type Type. Nil variables are randomly assigned an address when the program starts and variables are initialized. The Type Type is a new Type based on int.

Nil can only be compared with “pointer, channel, func, interface, map, and slice”, as noted in the source code comments. If you compare nil to other types, typecheck will report an error at compile time.

1.3. Why nil?

Go allocates memory with zeros in it (zeros are used to ensure that the allocated block contains all zeros). Makeslice/makechan/runtime.makechan/runtime.makechan/runtime.makechan/runtime.makechan/runtime.makechan/runtime.makechan/runtime.makechan/runtime.makechan/runtime.makechan/runtime.makechan In contrast, the default memory allocation behavior of C language is only memory allocation, the data can not make any assumptions, may be all 0, may be all 1.

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
Copy the code

However, some types, such as pointer and slice, do not have a clear representation of zero value. In this case, nil is needed to judge the zero value. That means is nil for pointer/channel/func/interface/map/zero slice type declaration in advance.

The second function is to assign objects to nil, which AIDS gc in memory reclamation, as discussed in go Map sharing. Or setting slice to nil frees the array of underlying references.

I’ve seen people say that when you assign an object to nil, the memory footprint goes to zero, but the size of the object printed out by printf is still the size of the object’s data structure. And print the address of the nil object, which becomes 0x0. In my personal opinion, it is more reasonable to say “the address of nil object points to nil” and “the memory usage of nil object is 0”. The printed result is not consistent, probably the verification posture is not correct.

1.4. Question 1

Can the following declaration/initialization code be executed?

var val = nil
fmt.Printf("%T\n",val)
Copy the code

— Error “Use of untyped nil”. Only a pointer/channel/func/interface/map/slice six types can be compared using nil, assignment. This line of code has so little information that the type is nondeterministic that the compiler can’t infer the nil expected type.

Second, compare with nil

For the understanding of nil implementation logic, I think the “Kishv” guru said very well, the excerpt is as follows

At the language level, the concept of nil is brought to you by the compiler. Not all types can be compared to nil or assigned to nil. Only variables of these six types can be compared to nil because that’s what the compiler decides. Similarly, you can’t assign a nil variable to an integer for the simple reason that the compiler won’t let you. So, nil is actually more accurately thought of as a trigger condition, so if the compiler sees a way to write a comparison to nil, it has to make sure that it’s within these six types, and if it’s assigned to nil, it has to make sure that it’s within these six types.

2.1 comparison of nil and nil

fmt.Println(nil == nil)
Copy the code

This can be interpreted as a comparison between two identifiers, and the error “Invalid operation: nil == nil” is reported.

2.2 Comparison of nil and Pointers

A pointer is an 8-byte block of memory whose value is the memory address pointing to an object.

var ptr *int
Copy the code

Comparing a pointer to nil determines “PTR == 0”.

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

A variable of a function type is also a pointer, the same logic as a nil comparison.

2.3 Comparison of nil and slice

Slice is a structure that is already allocated for both var and make initializations, so initialization variables can be used directly, such as the append operation. Some people might think that the slice variable initialized by var cannot be used directly like the map. This is not true, as can be seen from the panic information in the direct assignment operation: “Index out of range” instead of “Invalid memory address or nil pointer dereference” Var slice is equivalent to make(slice, 0, 0). (For new Slice, the result is a pointer and cannot be used directly.) Var s slice returns nil, but make(slice, 0, 0) returns nil.

func main()  {
   a := make([]int, 0, 0)
   if a == nil {
   }
   var b []int
   if b == nil {
   }
}
Copy the code

These are nil slices and empty slices. The difference is that the make function initializes slice’s underlying data structure, that is, it initializes the array that Slice relies on. The compiler uses “slice.array unsafe.Pointer == nil” as its criterion for slice == nil. That is, slice is nil if the underlying dynamic array pointer has no actual data. Len and CAP are not judged. The experimental code is as follows:

type sliceType struct { array unsafe.Pointer len int cap int } func main() { var a []byte ((*sliceType)(unsafe.Pointer(&a))).len = 0x3 ((*sliceType)(unsafe.Pointer(&a))).cap = 0x4 if a ! = nil { println("not nil") } else { println("nil") } }Copy the code

2.4 comparison of nil with Map /channel

Map /channel is a pointer, but points to a structure. Var initialization results in only a pointer variable, which cannot be directly written (if used, panic will be reported as a null pointer). The structure must be initialized with make to allocate memory before it can be used.

So a map/channel comparison to nil, which is also a pointer comparison, m1 is nil, m2 is not nil.

var m1 map[string]int
var m2 = make(map[string]int)
Copy the code

2.5. Comparison of nil and Interface

1. When is interface nil

Interface consists of two parts: Type and data (runtime.eface is the structure of an empty interface without a method, runtime.iface is the structure of an interface with a method, both of which are composed of these two parts). So to determine if interface is nil, you have to have both parts empty for interface to be nil. Maybe you just need to check if type is nil, because when type is not assigned, its data must be empty, but we haven’t done that yet, so I’ll leave it todo. A common error case is to assign interface to a nil *struct, thinking interface is also nil.

func main() { err := GetErr() fmt.Println(err == nil) } type Error interface { } type err struct { Code int64 Msg string  } func GetErr() Error { var res *Error return res }Copy the code

Of course, reflection can also be used to determine if only the data part is nil:

func IsNil(i interface{}) bool {
   vi := reflect.ValueOf(i)
   if vi.Kind() == reflect.Ptr {
      return vi.IsNil()
   }
   return false
}
Copy the code

2. A suggestion about error interface

Do not return concrete error types. Here is a bad case:

func main() { fmt.Println( GetErr() == nil) fmt.Println( WrapGetErr() == nil) } type Error interface { } type err struct  { Code int64 Msg string } func GetErr() *err { return nil } func WrapGetErr() Error { return GetErr() }Copy the code

3, When does interface equal nil *struct

For this code, you can judge the result:

type A interface{}
type B struct{}
var a *B

fmt.Println(a == nil)            // 1        
fmt.Println(a == (*B)(nil))      // 2
fmt.Println((A)(a) == (*B)(nil)) // 3
fmt.Println((A)(a) == nil)       // 4
Copy the code

The answer is true,true,true,false, 1/2/4. The answer is counter-intuitive: (A)(A) is not nil. The reason is the syntax of interface: when an interface compares a value with a type, it compares both type and value. Two objects are considered equal only when both type and value are equal.

Nil *struct not equal to nil interface

Francesc Campoy: nil *struct can also implement an interface, which acts as an interface and has default behavior. But nil interface doesn’t work.

func doSum(s Summer) int {
   if s == nil {  // use nil interface to signal default
      return 0
   }
   return s.Sum()
}
Copy the code

2.6. Nil is also typed

As follows, nil values of different types cannot be compared, except of course interfaces and structs.

func main() { var ptr *int64 = nil var cha chan int64 = nil var fun func() = nil var inter interface{} = nil var m map[string]string = nil var slice []int64 = nil fmt.Println(ptr == cha) fmt.Println(ptr == fun) fmt.Println(ptr == inter) fmt.Println(ptr == m) fmt.Println(ptr == slice) fmt.Println(cha == fun) fmt.Println(cha == inter) fmt.Println(cha  == m) fmt.Println(cha == slice) fmt.Println(fun == inter) fmt.Println(fun == m) fmt.Println(fun == slice) fmt.Println(inter == m) fmt.Println(inter == slice) fmt.Println(m == slice) }Copy the code

Running the code above captures the second question: Why can pointer types, channel types, and interface types be compared?

Iii. Make the zero value useful

3.1. What is the meaning of nil in Go?

  • Pointer points to an empty object
  • Slice underlying array is empty
  • The Map is not initialized
  • The Channel is not initialized
  • Function is not initialized
  • Interface assignment

3.2. Nil receiver is useful

If the *receiver(* means pointer object) is nil, its func (*T) funtion method can still be called, but func (T) funtion cannot be called. This is because when *receiver executes func (T) funtion, the compiler does a syntactic sugar: it makes a copy of *receiver and calls it again, and it cannot copy when *receiver==nil.

  • The temporary receiver variable cannot call func (*T) funtion because it cannot fetch the address.
  • Extension 2: * Receiver and receiver can use func (*T) funtion and func (T) funtion arbitrarily, because the compiler does syntax sugar.

3.3. Use of nil map

  • If the map is not initialized, read/delete errors are not reported, which reduces the judgment logic in the scenario where the map is read-only.
  • If the map is not initialized, it will panic.

3.4. Channel switch with nil

What’s wrong with this code?

func merge(out chan<- int, a, b <-chan int) { var aClosed, bClosed bool for ! aClosed || ! bClosed { select { case v, ok := <- a: if ! ok { aClosed = true; continue } out <- v case v, ok := <- b: if ! ok { bClosed = true; continue } out <- v } } close(out) }Copy the code

— THE CPU explodes.

Use nil channel to disable a select case:

func merge(out chan<- int, a, b <-chan int) { for a ! = nil || b ! = nil { select { case v, ok := <- a: if ! ok { a = nil; continue } out <- v case v, ok := <- b: if ! ok { b = nil; continue } out <- v } } close(out) }Copy the code

Nil channel and Closed channel

  • Panic occurs when a closed channel is written
  • Close A nil/closed channel will panic
  • Reading or writing a nil channel blocks forever

3.5, Nil function can be an indication of default values

func NewServer(logger function) {
   if logger == nil {
      logger = log.Printf  // default
   }
   logger.DoSomething...
}
Copy the code

Reference:

What is the result of the nil comparison of Go in depth? GopherCon2016 Understanding nil