为何打印含未初始化嵌入error的指针会触发nil指针错误?
先看你提供的代码和执行结果:
代码示例
package main import ( "log" "errors" ) type Danger struct { error } func main() { // the nil pointer issue has to do with struct embedding an error value that is nil d := &Danger{} log.Println(d) d = &Danger{errors.New("foobar")} log.Println(d) }
执行结果
2009/11/10 23:00:00 %!v(PANIC=runtime error: invalid memory address or nil pointer dereference) 2009/11/10 23:00:00 foobar
这个问题的核心在于Go的接口类型特性和结构体嵌入的方法提升机制,咱们一步步拆解:
结构体嵌入接口的方法提升
当你在Danger结构体里嵌入error接口时,error的所有方法(这里就是Error() string)会被自动提升为Danger类型的方法。也就是说,调用d.Error()时,实际是在调用嵌入的error接口的Error()方法。接口零值的特殊情况
Go的接口值包含两部分:动态类型(实际存储的类型)和动态值(实际存储的值)。当你初始化d := &Danger{}时,嵌入的error是接口的零值——也就是(nil, nil),既没有绑定具体类型,也没有具体值。
这里要注意和「具体类型的nil指针」区分:比如var err *errors.errorString = nil,它对应的接口值是(*errors.errorString, nil),调用err.Error()不会panic,因为方法是绑定到*errors.errorString类型上的,即使指针是nil也能执行方法(只要方法内部不访问nil指针的字段)。fmt打印的触发逻辑
log.Println底层依赖fmt包的打印逻辑。当打印一个结构体时,如果它拥有Error()方法(因为嵌入了error,Danger自动具备这个方法),fmt会尝试调用该方法生成输出字符串。
但此时d里的error接口是(nil, nil)状态,调用Error()方法时,Go找不到具体的类型来执行这个方法,就会触发nil指针解引用的panic。初始化后的正常情况
当你用errors.New("foobar")初始化Danger时,嵌入的error接口的动态类型是*errors.errorString,动态值是对应的字符串。此时调用Error()方法,会执行*errors.errorString类型的Error()方法,自然能正常返回字符串,不会panic。
简单总结:嵌入的接口零值调用方法会panic,而持有具体nil类型的接口调用方法不会——这是Go接口最容易踩的坑之一!
内容的提问来源于stack exchange,提问作者yangmillstheory

