对一个空的结构指针调用方法不会引发恐慌。为什么呢?

huangapple go评论64阅读模式
英文:

Calling a method on a nil struct pointer doesn't panic. Why not?

问题

以下是代码的中文翻译:

type MyError struct {
    errors []string
}

func (t *MyError) Error() string {
    if t == nil {
        fmt.Println("t指针为空")
        return ""
    }
    pointers := make([]string, 0, len(t.errors))
    for i, r := range t.errors {
        pointers[i] = r
    }
    sort.Strings(pointers)
    return fmt.Sprintf("解码错误,共有 %d 个错误:\n\n%s", len(t.errors), strings.Join(pointers, ","))
}

func main() {
    var err *MyError
    err.Error()  // 预期在这里引发 "panic: runtime error: invalid memory address or nil pointer dereference" 错误
}

变量errnil,所以调用err.Error()方法预期会引发"panic: runtime error: invalid memory address or nil pointer dereference"错误,但实际上方法调用成功了。为什么不会引发错误呢?

英文:
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
}

The variable err is nil so calling err.Error() method is expected to cause a panic "runtime error: invalid memory address or nil pointer dereference", but the method call succeeds. Why doesn't this panic?

答案1

得分: 29

Michael Jones解释得很好(作为答案复制):

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

以这种方式,在特定类型的nil指针上调用方法具有明确且逻辑清晰的含义。

熟悉vtable[]实现的人可能会觉得这很奇怪,但是,当以这种方式思考方法时,它甚至更简单且合乎逻辑。可以将:

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

理解为具有字面意义的:

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

在这个视角下,SometypeSomemethod()的函数体当然可以自由地测试它的(实际)第一个参数(p *Sometype)是否为nil。

英文:

Michael Jones explained this well (copied as answer):

> In Go the function to be called by the Expression.Name() syntax is
> entirely determined by the type of Expression and not by the
> particular run-time value of that expression, including nil.
>
> In this manner, the invocation of a method on a nil pointer of a
> specific type has a clear and logical meaning.
>
> Those familiar with vtable[] implementations will find this odd at
> first, yet, when thinking of methods this way, it is even simpler and
> makes sense. Think of:
>
> func (p *Sometype) Somemethod (firstArg int) {}
>
> as having the literal meaning:
>
> func SometypeSomemethod(p *Sometype, firstArg int) {}
>
> and in this view, the body of SometypeSomemethod() is certainly free
> to test it's (actual) first argument (p *Sometype) for a value of nil.

答案2

得分: 8

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

当为变量分配存储空间时,无论是通过声明还是通过new的调用,或者当通过复合字面量或make的调用创建新值时,如果没有提供显式初始化,变量或值将被赋予默认值。这样变量或值的每个元素都将设置为其类型的零值:布尔类型为false,整数类型为0,浮点数类型为0.0,字符串类型为"",指针、函数、接口、切片、通道和映射类型为nil。这种初始化是递归进行的,因此,例如,如果未指定值,则结构体数组的每个元素的字段将被清零。

因此,在您的代码中,var err *MyErrornil,即使 err = nil,您仍然可以调用 err.Error()

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

英文:

Please read: https://golang.org/ref/spec#The_zero_value

> When storage is allocated for a variable, either through a declaration
> or a call of new, or when a new value is created, either through a
> composite literal or a call of make, and no explicit initialization is
> provided, the variable or value is given a default value. Each element
> of such a variable or value is set to the zero value for its type:
> false for booleans, 0 for integers, 0.0 for floats, "" for strings,
> and nil for pointers, functions, interfaces, slices, channels, and
> maps
. This initialization is done recursively, so for instance each
> element of an array of structs will have its fields zeroed if no value
> is specified.

So in your code, var err *MyError is nil, you can call err.Error() is ok even err = nill,

**

> In Go, the function to be called by the Expression.Name() syntax is
> entirely determined by the type of Expression and not by the
> particular run-time value of that expression, including nil.

**

答案3

得分: 0

从C++/Java过来,这可能看起来很奇怪,另一个需要注意的地方是,持有空结构体的接口本身并不是空!

你可以在Go Playground上尝试一下:

type inf interface {
  test() string
}

type s struct {
}

func (t *s) test() string {
   return "nil struct with pointer receiver 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")
        }
}

对你来说,关键的一点是,Go的“继承”实际上是鸭子类型。接口被定义为能够持有任何有效值,而nil本身就是一个有效值!

英文:

Coming from C++/Java, this can look odd, another gotcha is that an interface holding a nil struct is not itself nil!

You can try this on go playground to get a hang of things:

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")
        }
}

The key thing to you: go 'inheritance' is actually duck typing. And interfaces are defined to be able hold any valid value, and nil itself is a valid value!

huangapple
  • 本文由 发表于 2017年2月15日 08:15:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/42238624.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定