In Go, however, 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 - copied
So we can call a method
using a struct instance which is nil.
Consider the following program:
package main
import "fmt"
type T struct {
V int
tt *T
}
func (t *T) hello() string {
return "world"
}
func main() {
var t *T = nil
fmt.Println(t, t.hello()) // <nil> world
fmt.Println(t, t.tt.hello()) // panic
}
Why fmt.Println(t, t.hello())
worked?
But
fmt.Println(t, t.tt.hello())
panicked?.
My understanding is that both t
and t.tt
are nil
pointers. So t.tt.hello()
should not panic as Calling a method on a nil struct pointer is allowed in golang.
My understanding is that both t and t.tt are nil pointers. So t.tt.hello() should not panic as Calling a method on a nil struct pointer is allowed in golang.
Your "understanding" is wrong.t.tt
should and does panic
.
Go 1.2 Release Notes (December 2013)
The language now specifies that, for safety reasons, certain uses of nil pointers are guaranteed to trigger a run-time panic. For instance, in Go 1.0, given code like
type T struct { X [1<<24]byte Field int32 } func main() { var x *T ... }
the nil pointer x could be used to access memory incorrectly: the expression x.Field could access memory at address 1<<24. To prevent such unsafe behavior, in Go 1.2 the compilers now guarantee that any indirection through a nil pointer, such as illustrated here but also in nil pointers to arrays, nil interface values, nil slices, and so on, will either panic or return a correct, safe non-nil value. In short, any expression that explicitly or implicitly requires evaluation of a nil address is an error. The implementation may inject extra tests into the compiled program to enforce this behavior.
Further details are in the design document.
In short, any expression that explicitly or implicitly requires evaluation of a nil address is an error.
Therefore, the following behavior is expected. The indirection for t.tt
through a nil
value of t
fails with a panic
.
package main
import "fmt"
type T struct {
V int
tt *T
}
func (t *T) hello() string {
return "world"
}
type A struct {
a int
}
func main() {
var t *T = nil
fmt.Println(t) // nil
fmt.Println(t.tt.hello()) // panic
}
Playground: https://play.golang.org/p/Szwx5MqNHkQ
Output:
<nil>
panic: runtime error: invalid memory address or nil pointer dereference
main.main()
/tmp/sandbox136049644/main.go:21 +0x84
nil
is the zero value for pointers
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
Source: https://golang.org/src/builtin/builtin.go?h=nil#L101
To understand nil
this video is fantastic
https://www.youtube.com/watch?v=ynoY2xz-F8s
Precisely speaking, t
is a nil pointer, and t.tt
doesn't exist at all. The panic you see is the result of dereferencing t
, not t.tt
. This is just obscured by the fact that t.tt
is (or would be, if t
were initialized) also a pointer.
This can be made more clear by accessing the V
field of t
:
func (t *T) foo() {
fmt.Println(t.V) // Will panic, if `t` is nil
}
The reason the first test doesn't panic is that calling a method on t
doesn't actually dereference t
. Calling t.Hello()
is roughly the equivalent of calling Hello(t)
, so won't panic unless/until t
is actually dereferenced within the function.
t
is nil,there is no t.tt
.
And t.hello()
is like hello(t)
, hello(nil)
don't panic, but t.tt
do.
Remember: a method is just a function with a receiver argument.