背景

由于工作中大多使用 Go 语言,笔者在学习 Java 的同时也会兼顾一些 Go 语言的知识。这篇文章记录两个笔者见过 Go 语言中非常隐蔽的坑,如果不是提前见到,日后工作中大概率遇到后会调很久的 bug !

简单的 for range 循环,你真的了解吗?

话不多说,先来看一份代码。逻辑很简单,我们来猜猜看它的输出是什么?

import "fmt"

type Person struct {
	name string
}

func main() {
	persons := []Person{{"zhangsan"}, {"lisi"}}
	newPersons := []*Person{}

	for _, person := range persons {
		newPersons = append(newPersons, &person)
	}

	println(newPersons[0].name)
	println(newPersons[1].name)

}

或许读者期待的输出是

zhangsan
lisi

然而事实上这段代码的输出是

lisi
lisi
person
import "fmt"

type Person struct {
	name string
}

func main() {
	persons := []Person{{"zhangsan"}, {"lisi"}}
	newPersons := []*Person{}

	for _, person := range persons {
		fmt.Printf("add=%p \n", &person)
		fmt.Println("name=" + person.name)
		newPersons = append(newPersons, &person)
	}

	//println(newPersons[0].name)
	//println(newPersons[1].name)

}
person
add=0xc000014250 
name=zhangsan
add=0xc000014250 
name=lisi
statements.ccdo_lowergofrontend/go/statements.cc/For_range_statement::do_lower() 
// The loop we generate:
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0; index_temp < len_temp; index_temp++ {
//     value_temp = for_temp[index_temp]
//     index = index_temp
//     value = value_temp
//     original body
// }
range
value_tempvalue_temp
newPersons =append(newPersons,&person)newPersonsvalue_templisi
range

err !=nil 判断竟然失效了 ?

先来看一段代码,逻辑同样不复杂。

type MyError struct {
	msg string
}

func (e *MyError) Error() string {
	return e.msg
}

func checkError(e error) {
	if e != nil {
		println("false")
	} else {
		println("true")
	}
}

func main() {
	var err *MyError = nil
	checkError(err) // e == nil ?
}
mainerre !=nil 
err
var err *NilError = nil
fmt.Printf("err=(%T,%v)", err, err)

// err=(*main.NilError,<nil>) 
err


e !=nil
fmt.Printf("err=(%T,%v)", nil, nil)

// err=(<nil>,<nil>) 

细心的读者已经发现了,本例中的 err 和 nil 的值一样,但类型不一样!

e !=nil


我们去检查源码,发现 error 实际上是一个接口。

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}
e !=nil
两个接口值相等,仅当它们都是 nil 值或者它们的动态类型相同并且动态值也根据这个动态类型的 == 操作相等

知道原因后,如何避免这个问题发生?

首先,当我们作为 producer ,明确地需要返回一个空的 error 的时候,直接返回 nil

func returnError() error {
   var e *NilError = nil
   // return e
   // do some processing
   return nil
   // return e     - > don't do this 
}

其次,当我们作为 consumer,需要检验 error 是否为空,可以参考本例的问题做出如下操作:忽略类型,检查 error 的值是否为空即可。

func IsNilError(c interface{}) bool {
   return c == nil || reflect.ValueOf(c).IsNil()
}


更多细节可以参考这里