This post first appeared on AT7H’s personal blog

In this article we will discuss The zero value, nil, and empty struct in Golang and some of their uses.

Zero value

Zero is the policy of always automatically setting a default initial value for your variables when you declare them (allocate memory) without explicitly initializing them.

First let’s take a look at The official specification for The zero value:

When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for numeric types, “” for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.

Based on this, we can conclude:

  • forValue types: Boolean type isfalse, the value type is0, the string is"", arrays and structures recursively initialize their elements or fields, meaning that their initial values depend on the element or field.
  • For reference types: nil, including pointer pointer, function, interface, slice, channel, map map.

In general, it is useful to assign default values to variables you declare, especially for elements or fields in your arrays and structures. This is a way to ensure security and correctness, and also to keep your code concise.

For example, the following example is a common one that contains two unexported fields in a Value structure and two unexported fields in sync.mutex. Since there is a default value of zero, we can simply use:

package main

import "sync"

type Value struct {
    mu sync.Mutex
    val int
}

func (v *Value)Incr(a){
    defer v.mu.Unlock()

    v.mu.Lock()
    v.val++
}

func main(a) {
    var i Value

    i.Incr()
}
Copy the code

Because slices are referential, their zero value is nil:

package main

import "fmt"
import "strings"

func main(a){
    var s []string

    fmt.Println(s, len(s), cap(s)) / / [] 0 0
    fmt.Println(s == nil) // true

    s = append(s, "Hello")
    s = append(s, "World")
    fmt.Println(strings.Join(s, ",")) // Hello, World
}
Copy the code

= syntax sugar declares and initializes variables, so is a true instance (the memory address assigned to it), not nil := syntax sugar declares and initializes variables, so is a true instance (memory address assigned to it), not nil:

package main

import "fmt"
import "reflect"

func main(a) {
    var s1 []string
    s2 := []string{} Var s2 = []string{}

    fmt.Println(s1 == nil) // true
    fmt.Println(s2 == nil) // false

    fmt.Println(reflect.DeepEqual(s1, s2)) // false

    fmt.Println(reflect.DeepEqual(s1, []string{}))  // false
    fmt.Println(reflect.DeepEqual(s2, []string{}))  // true
}
Copy the code

In addition, it is possible to call a method of this type for nil of an empty structure, which can also be used to simply provide default values:

package main

import "fmt"

const defaultPath = "/usr/bin/"

type Config struct {
    path string
}

func (c *Config) Path(a) string {
    if c == nil {
            return defaultPath
    }
    return c.path
}

func main(a) {
    var c1 *Config
    var c2 = &Config{
            path: "/usr/local/bin/",
    }
    fmt.Println(c1.Path(), c2.Path())
}
Copy the code

nil

For a developer just starting out with Golang, the first thing to do with nil would be to use it to check for errors, something like this:

func doSomething(a) error {
    return nil
}

func main(a){
    ifdoSomething() ! =nil {
        return err
    }
}
Copy the code

This is typical of Golang, which encourages developers to explicitly treat errors as return values. Now let’s talk about nil. There are similar definitions in other languages, such as NULL in C, C++, Java, etc., and None in Python, but nil in Goalng is different in many ways.

Nil is a pre-declared identifier (non-keyword reserved word) in Golang that is primarily used to represent zero values of reference types (Pointers, interfaces, functions, maps, slices, and channels) and their uninitialized values.

// [src/builtin/builtin.go](https://golang.org/src/builtin/builtin.go#L98)
//
// 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
Copy the code

Nil is the only untyped value in Golang that does not have a default type; it is not an undefined state. So you can’t use it like this:

a := nil
// cannot declare variable as untyped nil: a
Copy the code

It is wrong to assign a value to A that has no type nil. The compiler does not know what type it should assign to A.

It’s worth mentioning the well-known nil in Golang! = nil, let’s look at the following example:

var p *int
var i interface{}

fmt.Println(p)      // <nil>
fmt.Println(i)      // <nil>

fmt.Println(p == i) // false
Copy the code

According to? Why are they both nil but not equal?

Why is My Nil Error Value not equal to nil

func Foo(a) error {
    var err *MyError = nil
    if bad() {
        err = ErrBad
    }
    return err
}

func main(a) {
    err := Foo()
    fmt.Println(err)        // <nil>
    fmt.Println(err == nil) // false
}
Copy the code

The culprit is interface. The implementation of interfaces is beyond the scope of this article and will be shared later. The interface needs two basic attributes to determine a variable: Type and Value.

var p *int          // (T=*int,V=nil)
var i interface{}   // (T=nil,V=nil)

fmt.Println(p == i) // (T=*int, V=nil) == (T=nil, V=nil) -> false
Copy the code
func Foo(a) error {
    var err *PathError = nil  // (T=*PathError, V=nil)
    if bad() {
        err = ErrBad
    }
    return err  // This always returns a non-nil error
}

func main(a) {
    err := Foo()
    fmt.Println(err)        // <nil>
    fmt.Println(err == nil) // (T=*PathError, V=nil) == (T=nil, V=nil) -> false
}
Copy the code

Note: To avoid this problem, always use the error interface when returning an error, and never initialize an empty error variable that might be returned from a function.

Let’s change the above example and look at the following example:

var p *int              // (T=*int, V=nil)
var i interface{}       // (T=nil, V=nil)

fmt.Println(p == nil)   // true
fmt.Println(i == nil)   // true

i = p

fmt.Println(i == nil)     // (T=*int, V=nil) == (T=nil, V=nil) -> false
Copy the code

Interface values with nil underlying values in Go Tour

In the example I can be passed to a function with interface{} as an input parameter. It is not enough for you to check I == nil. So for null Pointers to interface types, sometimes you can’t safely rely on v == nil. Although this kind of checking is rare, it can sometimes crash your program. There are two ways to deal with this, you can compare types and values to nil or use reflect.

Remember: If any specific value is stored in the interface, then the interface will not be nil, as described in the law of reflection.

In addition, you may also be confused by the above example, why the following types can be directly compared and get accurate results:

var p *int              // (T=*int, V=nil)

fmt.Println(p == nil)   // true
Copy the code

This is because the compiler already knows the type of p, so it can be converted to p == (*int)(nil). But for interfaces, the compiler cannot determine the underlying type because it can be changed.

Empty structure

An empty structure is a structure type without any fields, for example:

type Q struct{}
var q struct{}
Copy the code

Since there are no fields, what good is it?

We know that the size of an instance of a structure (that is, the number of bytes in storage) is determined by the size and alignment of its fields, which helps with addressing speed. C has similar strategies. For details on Golang’s strategy, read Size and alignment.

Obviously, empty structures take up zero bytes of space:

var q struct {}
fmt.Println(unsafe.Sizeof(q)) / / 0
Copy the code

Since empty structures occupy zero bytes, no padding alignment is required, so empty structures nested by empty structures do not occupy storage space.

type Q struct {
        A struct{}
        B struct{
            C struct{}}}var q Q
fmt.Println(unsafe.Sizeof(q)) / / 0
Copy the code

If an array or slice uses an empty structure as an element, it is Orthogonality in Go:

var x [1000000000]struct{}
fmt.Println(unsafe.Sizeof(x)) / / 0

var y = make([]struct{},1000000000)
fmt.Println(unsafe.Sizeof(x))// 24, the associative array behind is 0
Copy the code

For an empty structure (or an empty array), the storage footprint is zero, so two different zero-size variables may have the same address in memory.

Take a look at the following examples:

var a, b struct{}
fmt.Println(&a == &b)  // true


c := make([]struct{}, 10)
d := make([]struct{}, 20)
fmt.Println(&c[0] == &d[1]) // true


type Q struct{}

func (q *Q)addr(a) { fmt.Printf("%p\n", q) }

func main(a) {
        var a, b Q
        a.addr()  // 0x5af5a60
        b.addr()  // 0x5af5a60
}

e := struct{} {}// Not zero, a real instance
f := struct{}{}
fmt.Println(e == f) // true
Copy the code

Note that this equality is possible, not certain.

For example, please refer to this issue for an explanation.

After all, you may be thinking that this doesn’t seem to have any practical uses, but here are two practical uses:

1. Use chan struct{} instead of chan bool to pass signals between goroutines. It’s easy to confuse the value with bool, true or false, but using chan struct{} makes it clear that we don’t care about the value, we only care about what happens, so it’s easier to make it clear.

Struct {}; struct {};

type Q struct {
  X, Y int
  _    struct{}
Copy the code

Q{X: 1, Y: 1} can be used, but Q{1, 1} will cause a compilation error: Too few values in struct Initializer, which will also help check the go Ver code.

reference

  • The Go Programming Language Specification
  • Golang Frequently Asked Questions
  • Why Golang Nil Is Not Always Nil? Nil Explained