This article started with a problem with the nil judgment in the code, and I’m going to start with the example here.

Let’s start with a piece of code that converts an object to a JSON string and returns it, where “” is returned if I ==nil.

func ToJSONString(i interface{}) string {
   if i == nil {
      return ""
   }
   bytes, _ := json.Marshal(i)
   return string(bytes)
}
Copy the code

This code doesn’t look like much of a problem at first glance, but there’s actually an implicit problem here that’s not easy to see. Before we get to that, let’s look at what’s wrong with this code

type Data struct {
   V int `json:"v"`
}

func TestToJSONString(t *testing.T) {
   a := assert.New(t)

   a.Equal("", ToJSONString(nil))

   var data *Data
   a.Equal("", ToJSONString(data))

   var k *int
   a.Equal("", ToJSONString(k))
}
Copy the code

The three assertions in this test code return:

Success
Expect: "", Actual: "null"
Expect: "", Actual: "null"
Copy the code

It’s confusing here, data should be nil, it looks like data==nil is false, and to confirm that, let’s introduce a piece of code.

var a *int = nil
var b interface{} = nil

fmt.Println("a == nil:", a == nil)
fmt.Println("b == nil:", b == nil)
fmt.Println("a == b:", a == b)
Copy the code

What do you think is the result here?

a == nil: true
b == nil: true
a == b: false
Copy the code

That’s too simple. Let’s look at a similar, slightly more complicated example

var a *int = nil
var b interface{} = a

fmt.Println("a == nil:", a == nil)
fmt.Println("b == nil:", b == nil)
fmt.Println("a == b:", a == b)
Copy the code

Again, what do you think the answer to this code is?

A == nil: true b == nil: false a == b: trueCopy the code

What’s going on?

First of all, we need to understand that in Go, each pointer has two basic information, the type of the pointer and the value of the pointer, I will use the form (type, value). In other words, something like a := nil won’t compile because it’s missing the type, so we need var a *int = nil. We can print the type of these Pointers in FMT:

var a *int = nil
var b interface{} = nil

fmt.Printf("a.type:%T\n", a) // a.type:*int
fmt.Printf("b.type:%T\n", b) // b.type:<nil>
Copy the code

Now that we know that interace{} defaults to nil, let’s pass a to B instead of hard-coding it, and look at the result:

var a *int = nil
var b interface{} = a

fmt.Printf("a.type:%T\n", a) // a.type:*int
fmt.Printf("b.type:%T\n", b) // b.type:*int
Copy the code

In other words, B now has a new type *int. So far you’ve seen some of the basic mechanics of types, but there are some questions that haven’t been answered yet

When you perform= =What happened?

Now that we’ve seen how variable types are determined, let’s look at the inner workings of ==.

var a *int = nil
var b interface{} = nil

fmt.Printf("a=(%T, %v)\n", a, a)
fmt.Printf("b=(%T, %v)\n", b, b)
fmt.Println("a == nil:", a == nil)
fmt.Println("b == nil:", b == nil)
fmt.Println("a == b:", a == b)
Copy the code

In the following code, I print both the type and the value of the variable. The result is as follows:

a=(*int, <nil>)
b=(<nil>, <nil>)
a == nil: true
b == nil: true
a == b: false
Copy the code

At first glance, this code may seem impossible, saying: a==nil, b==nil, but a! = b. The reality is that type determination is not only about the values of the two, but also about their types.

A = = nil / / is equivalent to: (* int, nil) = = (* int, nil) b = = nil / / is equivalent to: (nil, nil) = = a = = b (nil nil) / / is equivalent to: (*int, nil) == (nil, nil)Copy the code

When we write it this way, we can easily see that a! =b is true, but this information is not reflected in the code, and this is where misunderstandings can occur. So in this case, if you wanted to determine if both of them are nil, you could write this

if a == nil && b == nil {
  // do something
}
Copy the code

Let’s take a look at the confusing example where the results are directly following the code

var a *int = nil
var b interface{} = a

fmt.Printf("a=(%T, %v)\n", a, a)   // a=(*int, <nil>)
fmt.Printf("b=(%T, %v)\n", b, b)   // b=(*int, <nil>)
fmt.Println("a == nil:", a == nil) // a == nil: true
fmt.Println("b == nil:", b == nil) // b == nil: false
fmt.Println("a == b:", a == b)     // a == b: true
Copy the code

Now if we look at b==nil, it’s a lot clearer:

b == nil // Equivalent to (*int, nil) == (nil, nil)
Copy the code

Now, you might be wondering, why is the type nil on the right-hand side of the equation nil nil? This is because when b is specified as type interface{}, there is no way to determine its true type, and its type may change as the program runs, so its type defaults to nil. Further, we can look at how a hard-coded number compares. Numbers infer their own types from context. Here’s a concrete example:

var a int = 12
var b float64 = 12
var c interface{} = a

fmt.Println("a==12:", a == 12) // true => (int, 12) == (int, 12)
fmt.Println("b==12:", b == 12) // true => (float64, 12) == (float64, 12)
fmt.Println("c==12:", c == 12) // true => (int, 12) == (int, 12)
fmt.Println("a==c:", a == c)   // true => (int, 12) == (int, 12)
fmt.Println("b==c:", b == c)   // false => (float64, 12) == (int, 12)
Copy the code

One important point to note is that when 12 is compared to an interface{}, it is converted to (int, 12) by default. Similarly, interface{} is also cast to (nil, nil), as shown in the following code

var b float64 = 12
var c interface{} = b

fmt.Println("c==12:", c == 12) // c==12: false
fmt.Printf("c=(%T,%v)\n", c, c) / / c = (float64, 12)
fmt.Printf("hard-coded=(%T,%v)\n".12.12) // hard-coded=(int,12)
Copy the code

So let’s go back to our original nil judgment

Let’s go back to the code that had the problem in the first place, and it’s a lot clearer now

func ToJSONString(i interface{}) string {
   if i == nil {
      return ""
   }
   bytes, _ := json.Marshal(i)
   return string(bytes)
}

func TestToJSONString(a) {
  // ...
  var data *Data
  a.Equal("", ToJSONString(data))
}
Copy the code

In line 2, the judgment I ==nil is equivalent to (*Data, nil) == (nil, nil), which is clearly not true. Of course, there are other ways to generate such an equation judgment, which requires the Reflect package, as follows:

func ToJSONString(i interface{}) string {
   if i == nil || (reflect.ValueOf(i).Kind() == reflect.Ptr && reflect.ValueOf(i).IsNil()) {
      return ""
   }
   bytes, _ := json.Marshal(i)
   return string(bytes)
}
Copy the code