Go: nil value comparison
Tags: go
Published: 2020-07-04 14:46:06

เนื่องจากค่า nil ใน Go สามารถเป็นค่าที่กำหนดให้กับหลาย type ได้ ดังนั้นการเปรียบเทียบค่า nil ด้วย operator `==` และ `!=` จึงขึ้นอยู่กับ type ด้วยเช่นกัน ถ้าเราเขียนโค้ดแบบนี้

```go
package main

import (
	"fmt"
)

func main() {	
	fmt.Println(nil == nil)
	fmt.Println(nil != nil)
}
```

แล้ว compile ดูเราจะเจอ compile error แบบนี้

```
# command-line-arguments
./main.go:14:18: invalid operation: nil == nil (operator == not defined on nil)
./main.go:15:18: invalid operation: nil != nil (operator != not defined on nil)
```

นั่นคือโดยตัวค่า `nil` เองนั้นเปรียบเทียบกันไม่ได้ เพราะว่าถ้าเราจับ `nil` ไปเปรียบเทียบตัว compiler จะไม่รู้ type ของ `nil` เลย

ทีนี้ type ใน Go ที่สามารถมีค่า `nil` ได้ประกอบไปด้วย

* pointer ของ type ใดๆ
* slice
* map
* channel
* function
* interface

ทีนี้ถ้าผมเขียนโค้ดแบบนี้เพื่อทดสอบการเปรียบเทียบค่า `nil` ของแต่ละ type ด้านบน เริ่มจาก pointer

```go
package main

import (
	"fmt"
)

func main() {
	var nilValue *string = nil
	fmt.Println(nilValue == nilValue)
	fmt.Println(nilValue != nilValue)
}
```

เมื่อ compile จะ compile ได้และ run จะได้

```sh
> go run main.go 
true
false
```

นั่นคือ pointer type เดียวกัน ที่มีค่า `nil` สามารถเปรียบเทียบกันได้

ต่อไปเป็น slice

```go
package main

import (
	"fmt"
)

func main() {
	var nilValue []string = nil
	fmt.Println(nilValue == nilValue)
	fmt.Println(nilValue != nilValue)
}
```

เมื่อ compile จะ error แบบนี้

```sh
> go run main.go 
# command-line-arguments
./main.go:9:23: invalid operation: nilValue == nilValue (slice can only be compared to nil)
./main.go:10:23: invalid operation: nilValue != nilValue (slice can only be compared to nil)
```

นั่นหมายถึงว่าตัวแปรของ type slice ที่มีค่า `nil` จะเปรียบเทียบกับตัวแปร type slice อื่นๆไม่ได้ แม้ว่ามันจะเก็บค่า `nil` เหมือนกันก็ตาม เห็นโค้ดที่เปรียบเทียบกับตัวมันเองแบบด้านบนก็ไม่ได้ แต่สามารถเปรียบเทียบกับ `nil` โดยตรงได้เช่น

```go
package main

import (
	"fmt"
)

func main() {
	var nilValue []string = nil
	fmt.Println(nilValue == nil)
	fmt.Println(nilValue != nil)
}
```

เมื่อ compile และ run แล้วจะได้

```sh
> go run main.go 
true
false
```

ต่อไปเป็น map

```go
package main

import (
	"fmt"
)

func main() {
	var nilValue map[string]string = nil
	fmt.Println(nilValue == nilValue)
	fmt.Println(nilValue != nilValue)
}
```

เมื่อ compile จะ error แบบนี้

```sh
> go run main.go 
# command-line-arguments
./main.go:9:23: invalid operation: nilValue == nilValue (map can only be compared to nil)
./main.go:10:23: invalid operation: nilValue != nilValue (map can only be compared to nil)
```

นั่นหมายถึงว่าตัวแปรของ type map ที่มีค่า `nil` จะเปรียบเทียบกับตัวแปร type map อื่นๆไม่ได้ แม้ว่ามันจะเก็บค่า `nil` เหมือนกันก็ตาม เห็นโค้ดที่เปรียบเทียบกับตัวมันเองแบบด้านบนก็ไม่ได้ แต่สามารถเปรียบเทียบกับ `nil` โดยตรงได้เช่น

```go
package main

import (
	"fmt"
)

func main() {
	var nilValue map[string]string = nil
	fmt.Println(nilValue == nil)
	fmt.Println(nilValue != nil)
}
```

เมื่อ compile และ run แล้วจะได้

```sh
> go run main.go 
true
false
```

ต่อไปเป็น channel

```
package main

import (
	"fmt"
)

func main() {
	var nilValue chan string = nil
	fmt.Println(nilValue == nilValue)
	fmt.Println(nilValue != nilValue)
}
```

เมื่อ compile และ run แล้วจะได้

```sh
> go run main.go 
true
false
```

นั่นคือ channel type เดียวกัน ที่มีค่า `nil` สามารถเปรียบเทียบกันได้

ต่อไปเป็น function

```go
package main

import (
	"fmt"
)

func main() {
	var nilValue func() = nil
	fmt.Println(nilValue == nilValue)
	fmt.Println(nilValue != nilValue)
}
```

เมื่อ compile จะ error แบบนี้

```sh
> go run main.go 
# command-line-arguments
./main.go:9:23: invalid operation: nilValue == nilValue (func can only be compared to nil)
./main.go:10:23: invalid operation: nilValue != nilValue (func can only be compared to nil)
```

นั่นหมายถึงว่าตัวแปรของ type function ที่มีค่า `nil` จะเปรียบเทียบกับตัวแปร type function อื่นๆไม่ได้ แม้ว่ามันจะเก็บค่า `nil` เหมือนกันก็ตาม เห็นโค้ดที่เปรียบเทียบกับตัวมันเองแบบด้านบนก็ไม่ได้ แต่สามารถเปรียบเทียบกับ `nil` โดยตรงได้เช่น

```go
package main

import (
	"fmt"
)

func main() {
	var nilValue func() = nil
	fmt.Println(nilValue == nil)
	fmt.Println(nilValue != nil)
}
```

เมื่อ compile และ run แล้วจะได้

```sh
> go run main.go 
true
false
```

ต่อไปเป็น interface

```go
package main

import (
	"fmt"
)

func main() {
	var nilValue interface{} = nil
	fmt.Println(nilValue == nilValue)
	fmt.Println(nilValue != nilValue)
}
```

เมื่อ compile และ run แล้วจะได้

```sh
> go run main.go 
true
false
```

นั่นคือ interface type เดียวกัน ที่มีค่า `nil` สามารถเปรียบเทียบกันได้

ถ้าจะสรุปก็คือ จะมีทั้งตัวแปรของ type ที่สามารถมีค่า `nil` แล้วสามารถเอาตัวแปรไปเปรียบเทียบกับตัวแปรอื่นที่มีค่า `nil` ได้ กับ อีกแบบคือเปรียบเทียบได้แต่กับ `nil` โดยตรง เปรียบเทียบกับตัวแปรอื่นไม่ได้

type ที่สามารถเปรียบเทียบตัวแปรที่มีค่า `nil` กับตัวแปรอื่นได้ ได้แก่

* pointer
* interface
* channel

type ที่สามารถเปรียบเทียบตัวแปรที่มีค่า `nil` กับ `nil` โดยตรงได้เท่านั้นได้แก่

* slice
* map
* function

สุดท้ายอีกเรื่องสำหรับ `nil` คือ `nil` ไม่ใช่ keyword ใน Go แต่เป็นชื่อที่ถูกกำหนดค่าเริ่มต้นเป็นค่าพิเศษค่านึงมาแล้ว ดังนั้น `nil` จึงสามารถถูกเอาไปใช้เป็นชื่อของตัวแปรได้ เช่นเราสามารถเขียนโค้ดนี้แล้ว compile และ run ได้

```go
package main

import (
	"fmt"
)

func main() {
	nil := 0
	fmt.Println(nil == nil)
	fmt.Println(nil != nil)
}
```

แต่อย่างไรก็ตาม อย่าเขียนแบบนี้ในโปรแกรมของเราเลยจะดีกว่า เพราะจะชวนให้เพื่อนๆในทีด่าได้ง่ายๆ :D