在nil结构指针上调用方法并不会引起恐慌。为什么不?

时间:2017-02-15 00:15:59

标签: go struct

type MyError struct {
    errors []string
}

func (t *MyError) Error() string {
    if t == nil {
        fmt.Println("t ptr empty")
        return ""
    }
    pointers := make([]string, 0, len(t.errors))
    for i, r := range t.errors {
        pointers[i] = r
    }
    sort.Strings(pointers)
    return fmt.Sprintf("n error(s) decoding:\n\n%s", len(t.errors), strings.Join(pointers, ","))
}

func main() {
    var err *MyError
    err.Error()  // expected "panic: runtime error: invalid memory address or nil pointer dereference" here
}

变量err为nil所以调用err.Error()方法会导致恐慌"运行时错误:无效的内存地址或nil指针取消引用",但方法​​调用成功。为什么不会出现这种恐慌?

3 个答案:

答案 0 :(得分:5)

Michael Jones explained this well(复制为答案):

  

在Go中,Expression.Name()语法调用的函数是   完全取决于表达的类型而不是表达式   该表达式的特定运行时值,包括nil。

     

以这种方式,在一个nil指针上调用一个方法   具体类型具有明确的逻辑含义。

     

那些熟悉vtable []实现的人会发现这很奇怪   首先,当以这种方式思考方法时,它甚至更简单   说得通。想一想:

func (p *Sometype) Somemethod (firstArg int) {} 
     

具有字面含义:

func SometypeSomemethod(p *Sometype, firstArg int) {}
     

在这个视图中,SometypeSomemethod()的主体肯定是免费的   测试它的(实际)第一个参数(p * Sometype)的值为nil。

答案 1 :(得分:3)

请阅读:https://golang.org/ref/spec#The_zero_value

  

通过声明为变量分配存储空间时   或者通过a来调用new,或者创建新值时   复合文字或make的调用,没有明确的初始化   提供,变量或值被赋予默认值。每个元素   将此类变量或值设置为其类型的零值:   布尔值为假,整数为0,浮点数为0.0,""对于字符串,   和 nil指针,函数,接口,切片,通道和   图即可。这种初始化是递归完成的,例如每个   如果没有值,则结构数组的元素将使其字段归零   已指定。

因此,在您的代码中,var err *MyErrornil,您可以致电err.Error()即可err = nill

**

  

在Go中,Expression.Name()语法调用的函数是   完全取决于表达的类型而不是表达式   该表达式的特定运行时值,包括nil。

**

答案 2 :(得分:0)

来自C ++ / Java,这看起来很奇怪,另一个陷阱是拥有nil结构的接口本身并不是nil!

您可以在运动场上尝试一下以获取更多乐趣:

type inf interface {
  test() string
}

type s struct {
}

func (t *s) test() string {
   return "nil struct with pointer reciever can still call method\n"
}

func main() {
    var sTest *s
    if sTest == nil {
       fmt.Printf("sTest is Nil\n")
    }
    
    fmt.Printf("%s", sTest.test())
    
    var fTest inf
    if fTest == nil {
       fmt.Printf("fTest is nil\n")
    }
    
        fTest = sTest
        if fTest != nil {
           fmt.Printf("interface holding nil is not nil!\n")
        }
}

您要注意的关键是:“继承”实际上是鸭子键入。接口被定义为可以容纳任何有效值,而nil本身就是有效值!