一个结构体, 具有指针型方法, 但是这个结构体的指针是nil, 调用结构体型方法报错, 调用指针型方法不会报错.
较好的回答,参考: https://groups.google.com/g/golang-nuts/c/wcrZ3P1zeAk/m/WI88iQgFMvwJ?pli=1
I think the question is really "but how would you inspect the object pointer to find and dispatch the function, as in C++ vtable." We might answer this from that angle:
Method dispatch, as is used in some other languages with something like objectpointer->methodName() involves inspection and indirection via the indicated pointer (or implicitly taking a pointer to the object) to determine what function to invoke using the named method. Some of these languages use pointer dereferencing syntax to access the named function. In any such cases, calling a member function or method on a zero pointer understandably has no valid meaning.
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. 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. Note though that the calling site invoking on a nil value must have a context of the expected type. An effort to invoke an unadorned nil.Somemethod would not work in Go because there is be no implicit "Sometype" for the typeless value nil to expand the Somemethod() call into "SometypeSomemethod()"
方法值:
methodV := someStruct.SomeMethod
那么这方法就和这个接收器绑定了,可以传递给其他,帮助修改绑定的接收器的内部状态.
方法表达式:
methodExpr := SomeStuct.SomeMethod
可以参考: https://www.cnblogs.com/phpper/p/12370086.html
方法值和方法表达式, 都可以有结构体和指针型.且结构体型都会复制结构体, 结构体型的方法值每次调用都会复制一次, 且是基于创建方法值的时候, struct的值进行复制的,就像是一次胚胎冷冻.
sFunc4()
fmt.Println("invoke another time: \n")
sFunc4() // 这两次调用都会复制一次结构体,其内存地址不同
关于 nil inteface(接口零值)
https://go.dev/tour/methods/12
另外在这个仓库里 : https://github.com/ksimka/go-is-not-good
, 进一步吐槽了这个设计: nil pointer are not entirely nil. 但是对应的网页内容丢失了
可以看: https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/#nil-interface-values
(给了不错的example)
也可以参考: https://codefibershq.com/blog/golang-why-nil-is-not-always-nil (给了解决方)
为防止内容再次丢失, 直接复制过来:
imageWhy Golang Nil Is Not Always Nil? Nil Explained
imageNewcoming developers starting their adventure with Golang are suprised by differencies that Golang has. One of them is the nil value...
Many times developers writing Golang code uses error value check whether the error was raised. It could look like this:
func do() error {
return nil
}
func main(){
if do() != nil {
return err
}
}
These constructs are idiomatic to Golang, as it encourages developers to explicitly return errors as values and handle them as usual variable. But this post is not about errors, its about nil values.
Nil represents a zero value in Golang.
As you may already know, zero values are the "default" value for defined variables in Go. This means that some types doesn't cannot hold empty values, instead acquiring zero values upon initialization to get rid of the problem of checking values for emptyness. This rule translates into behaviour descirbed below, defining a variable initializes it with empty value.
var i int // 0
var t bool // false
type T struct {
v string
}
var t T // {""}
This behaviour applies to all types in Go besides pointers, slices, maps, channels, functions, interfaces.
var s []int // s == nil -> true
var m map[string]string // m == nil -> true
var p *int // p == nil -> true
var c chan int // c == nil -> true
var f func() // f == nil -> true
type T struct {
p *int
}
var t T // t.p == nil -> true
var i interface{} // i == nil -> true
var a = nil // compile error: use of untyped nil
So far so good, there is no confusion here. Just to point out, I'm defining an i
variable which will hold a value that implements empty interface, which is satisfied by all types in Go. I could have used any other interface with the same effect (nil upon initialization). The last example shows that nil is not a type therefore it cannot be used to be assigned to a value without a type.
The confusion comes when one want to use a construct like this:
var p *int
var i interface{}
i = p
if i != nil {
fmt.Println("not a nil")
}
//the code outputs "not a nil"
What happened here? We can clearly see (and based on previous rules) that both p
and i
has value nil
but the condition is evaluated into false. This is not a bug it is a legitimate Golang behaviour.
What you should know is that interface is the reason of all the mess. The structure behind Golang interfaces holds two values - type and value. The type being a conrete type behind an interface (if any is assigned) and the type's value. To illustrate let's take a look at annotated examples that will help you understand Golang interfaces.
var p *int // (type=*int,value=nil)
var i interface{} // (type=nil,value=nil)
if i != p { // (type=*int,value=nil) != (type=nil,value=nil)
// to successfully compare these values, both type and value must match
fmt.Println("not a nil")
}
//the code outputs "not a nil"
Another thing to consider is that hardcoded nil
is always behaving like unassigned interface which is - (type=nil, value=nil). This can translate into another "weird" bahaviour, but once you understand the mechanics behind it you will be able to use it effectively.
var p *int // (type=*int,value=nil)
var i interface{} // (type=nil,value=nil)
if i != nil { // (type=nil,value=nil) != (type=nil,value=nil)
fmt.Println("not a nil")
}
i = p // assign p to i
// a hardcoded nil is always nil,nil (type,value)
if i != nil { // (type=*int,value=nil) != (type=nil,value=nil)
fmt.Println("not a nil")
}
//the code outputs "not a nil" only once
The above example is the most confusing and may lead unexpected program behaviour since i
variable can be passed along to another function which takes interface{}
as input type parameter, therefore checking it only for basic i == nil
will not suffice. What is the fix for that?
There are two solutions, one is to compare the value with typed nil value and the second one is using reflection package. Take a look at the example:
import "reflect"
import "unsafe"
func do(v interface{}){
if v == nil {
// this may not always work
// if the value is (type=*int,value=nil)
// the condition will not trigger
// because hardcoded `nil` is (type=nil,value=nil)
}
if v == (*int)(nil){
// one solution is to compare this value
// with concrete type that has zero (nil) value
// but this may lead to code duplication
// and very hard to read logic if you want
// to check multiple cases
}
if reflect.ValueOf(v).IsNil() {
// https://golang.org/pkg/reflect/#Value.IsNil
// by using `reflect` package we can check
// whether a value is nil but only if it is one
// of those 5 types that can be nil.
// So be careful using it (it panics)
}
if (*[2]uintptr)(unsafe.Pointer(&v))[1] == 0 {
// there is also more unsafe way
// it checks the value part of an interface
// directly for zero
// it also doesn't panic if the value
// is not one of 5 `nil able` types
}
}
Summing this up, the most confusing part is where you are trying to check whether the interface holds empty value (pointer, slice, function, interface, channel, map). In that case you must remember that you cannot safely rely on v == nil
comparison. On the other hand why is it that you can safely compare those types against handwritten nil like the example below and get accurate results?
var i *int
fmt.Println(i == nil) // prints `true`
In the example above the compiler does not the conrete type and can help you type the written nil
so basically this translates to compiler doing i == (*int)(nil)
. With interface the compiler is not sure about the underlying type because it can change any time (you can assign string and then pointer to the same interface variable) leading to really unexpected program behaviour.
I hope I've explained all the mystics about nil
value in Golang and now you know about every edge corner that you should cover in your project.
Benchmarks
Just for the closure, here are three benchmarks that shows the performance impact when using different nil checking strategies, I've used simple counter incrementation to be sure that the condition is actually done by the runtime (the compiler may cut the if statement if it cannot prove it has any effect on the program behaviour)
package main
import (
"reflect"
"testing"
"unsafe"
)
func BenchmarkSimple(b *testing.B) {
a := 0
for i := 0; i <= b.N; i++ {
var v interface{}
v = (*int)(nil)
if v == nil {
a++
} else {
a++
}
}
}
func BenchmarkNilPointer(b *testing.B) {
a := 0
for i := 0; i <= b.N; i++ {
var v interface{}
v = (*int)(nil)
if v == (*int)(nil) {
a++
} else {
a++
}
}
}
func BenchmarkReflect(b *testing.B) {
a := 0
for i := 0; i <= b.N; i++ {
var v interface{}
v = (*int)(nil)
if v == reflect.ValueOf(v).IsNil() {
a++
} else {
a++
}
}
}
func BenchmarkUnsafe(b *testing.B) {
a := 0
for i := 0; i <= b.N; i++ {
var v interface{}
v = (*[2]uintptr)(unsafe.Pointer(&v))[1]
if v == (*int)(nil) {
a++
} else {
a++
}
}
}
The results:
goos: linux
goarch: amd64
BenchmarkSimple 1000000000 0.270 ns/op
BenchmarkNilPointer 1000000000 0.277 ns/op
BenchmarkReflect 484543268 2.44 ns/op
BenchmarkUnsafe 1000000000 1.06 ns/op
As can we see there is about 10 times bigger latency when comparing to nil using reflect package and about 3 times when it comes to comparison using unsafe package.
From my experience the above nil checking gotchas doesn't occur very often because mostly we are dealing with empty interfaces in our code. The most common case might appear when developer are trying to return error values of their kind from a function that does not get assigned a value (only initialized). Take a look at a last example.
// example #1
type myerr string
func (err myerr) Error() string {
return "an error ocurred: " + err
}
func do() error {
var err *myerr
//do logic...
return err // might return nil pointer
}
func main() {
err := do()
print(err == nil) // prints `false` because it is nil pointer
}
// example #2
type myerr string
func (err myerr) Error() string {
return "an error ocurred"
}
func do() *myerr {
return nil // returns nil pointer
}
func wrap() error {
return do() // the information about nil pointer is dissolved
}
func main() {
err := wrap()
print(err == nil) //prints `false` because underneath is nil pointer not empty interface
}
The solution is simple - always use error
interface when returning an error from function and never initialize an empty error variable that might be return from function (as nil
).This makes the code more readable and generic but also avoid the situations above.
Thank you for your time spent reading the article and I hope it resolved some of the issues about nil checking in Golang.
Code Fibers provides services for Golang development and consulting, I invite you to visit our website and contact also get to know how we work and what is our exprience
网友评论