英文:
why does golang nil pointer dereference happen in submethods
问题
我有这段代码:
package main
import "fmt"
type Foo struct {
Bar string
}
func (f *Foo) level4() {
fmt.Printf("Bar = %s\n", f.Bar)
}
func (f *Foo) level3() {
f.level4() // 在这里发生恐慌,向下两级
}
func (f *Foo) level2() {
f.level3()
}
func (f *Foo) level1() {
f.level2()
}
type FooWrapper struct {
foo *Foo
}
func main() {
w := FooWrapper{}
w.foo.level1() // 预期在这里发生恐慌,因为foo是nil
}
按预期,运行这段代码会得到以下结果:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47f454]
然而,我预期空指针解引用会发生在w.foo.level1()
处,因为foo
是nil
。但实际上,它调用了级别1、2和3,并在那里发生了恐慌。为什么会这样呢?
英文:
I have this code
package main
import "fmt"
type Foo struct {
Bar string
}
func (f *Foo) level4() {
fmt.Printf("Bar = %s\n", f.Bar)
}
func (f *Foo) level3() {
f.level4() // panics here, 2 levels down
}
func (f *Foo) level2() {
f.level3()
}
func (f *Foo) level1() {
f.level2()
}
type FooWrapper struct {
foo *Foo
}
func main() {
w := FooWrapper{}
w.foo.level1() // expected it to panic here, since foo is nil
}
As expected, running this gives
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47f454]
However, I expected the nil pointer dereference to happen at w.foo.level1()
, since foo
is nil
. Instead, it calls levels 1, 2 and 3 and panics there. Why is this the case?
答案1
得分: 1
为什么会这样呢?
因为w.foo.level1()是有效的语句,类似地,f.level2()、f.level3()和f.level4()也是有效的语句。
试试这个:
func (f *Foo) level1() {
println("Hello Go")
f.level2()
}
它会打印出"Hello Go",并调用f.level2(),还可以看到最后一次调用level4:
func (f *Foo) level4() {
println("Hello Go 4")
fmt.Printf("Bar = %s\n", f.Bar)
}
它会打印出"Hello Go 4",但在下一行会出现panic,显示错误的追踪信息,或者你可以说是错误的起源。
英文:
Why is this the case?
Because w.foo.level1() is valid statement and similarly f.level2(), f.level3(), f.level4() are also a valid statement.
try this
func (f *Foo) level1() {
println("Hello Go")
f.level2()
}
it will print Hello Go
and call f.level2()
and also see last call level 4
func (f *Foo) level4() {
println("Hello Go 4")
fmt.Printf("Bar = %s\n", f.Bar)
}
it will print Hello Go 4
but panic on next line it is showing the trace or you can say origin of error
答案2
得分: 0
任何指针类型的默认值都是nil
。
当你使用以下方式构造对象时:
w := FooWrapper{}
foo
的值将被赋予默认的nil
。
要解决这个问题,你应该为foo
传递一个值。
w := FooWrapper{foo: &Foo{Bar: "bar"}}
为了避免这个问题,我通常会创建一些构造函数。
func NewFooWrapper() *FooWrapper {
return &FooWrapper{foo: &Foo{Bar: "bar"}}
}
w := NewFooWrapper()
或者,如果foo
的值不能硬编码:
func NewFooWrapper(foo *Foo) *FooWrapper {
return &FooWrapper{foo: foo}
}
w := NewFooWrapper(&Foo{Bar: "bar"})
从编码的角度来看,这样更加明确,可以防止在代码的其他地方出现这些错误。
英文:
The default value of any pointer type is nil
.
As you are constructing your object using
w := FooWrapper{}
The value of foo
will be assigned the default nil
.
To resolve the problem you should pass a value for foo
.
w := FooWrapper{foo: &Foo{Bar: "bar"}}
What I usually do to prevent this issue is to make some constructor function.
func NewFooWrapper() *FooWrapper {
return &FooWrapper{foo: &Foo{Bar: "bar"}}
}
w := NewFooWrapper()
Or in case the value of foo
can't be hardcoded.
func NewFooWrapper(foo *Foo) *FooWrapper {
return &FooWrapper{foo: foo}
}
w := NewFooWrapper(&Foo{Bar: "bar"})
This makes it more explicit from a coding point of view preventing these errors in other places of code.
答案3
得分: -1
你在主函数中没有正确初始化FooWrapper
。在FooWrapper
类型的变量w
中,foo
字段的类型是*Foo
,但是它的值是nil
,因为你没有为它赋值,而指针类型变量的默认值在Go中是nil
。
正确的初始化方式如下:
w := FooWrapper{
foo: &Foo{
Bar: "bar",
},
}
在playground中运行,输出结果为:
Bar = bar
level1()
、level2()
和level3()
中没有触发panic的原因是因为它们没有使用Foo
的字段。而在level4()
中,panic的原因不是函数调用,而是在打印语句中使用了f.Bar
。
在Go中,
如果接口本身的具体值为nil,则会使用nil接收者调用方法。
你可以在https://go.dev/tour/methods/12中找到一个简单的例子。
在Go规范文档的Calls部分也有对此的详细解释。
如果x是可寻址的,并且
&x
的方法集包含m,则x.m()是(&x).m()
的简写形式。
英文:
You did not initialize the FooWrapper
in your main function correctly. Your foo
field with type *Foo
is nil
inside w
variable type of FooWrapper
because you have not assigned a value for that and the default value for a pointer type variable is nil
in Go
Simple correct initialization as follows
w := FooWrapper{
foo: &Foo{
Bar: "bar",
},
}
run in playground
output :
Bar = bar
The reason that panic doesn't come in level1()
, level2()
, level3()
is because they are not using any Foo
's fields inside those functions. And also in level4()
panic occurs not because of the function call, but because it's using f.Bar
in print.
In Go,
> If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
You can find a simple example for this in https://go.dev/tour/methods/12
And also you can find a great explanation for this in Go specs documentation under Calls
> A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论