再议struct的value method和pointer method
以一个例子开头
package main
import (
"fmt"
_ "unsafe"
_ "reflect"
)
type MyInterface interface {
foo()
}
type MyStruct struct {
ii int64
}
func (m * MyStruct) foo() {
fmt.Println(m.ii);
m.ii ++
}
func Hello(p MyInterface) {
p.foo();
}
func main() {
m := MyStruct { 10 }
Hello(m)
fmt.Println(m.ii);
}
这段代码编译就会出错
$ go build main.go
# command-line-arguments
./main.go:29: cannot use m (type MyStruct) as type MyInterface in argument to Hello:
MyStruct does not implement MyInterface (foo method has pointer receiver)
意思是说MyStruct没有实现MyInterface说声称的方法foo,因为foo被声明成了pointer receiver,而实际需要的是value receiver。
分析原因
golang官方文档对value method和pointer method有一个解释:
https://golang.org/doc/effective_go.html#pointers_vs_values
The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.
This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake.
解释一下就是
- value method 可以被 pointer和value 对象调用,而
pointer method 只能被 pointer 对象调用
func (m * MyStruct) foo1();
func (m MyStruct) foo2();
m := MyStruct { 10 }
n := &m
//m.foo1(), compiler will fail this line, because foo1() is pointer method, cannot be called from value object m.
m.foo2() // notice, value modification will be discarded
n.foo1() // notice, value modification will be kept in m
n.foo2() // notice, value modification will be discarded
- 原因是:pointer method会修改对象的值,而value method不会,所以如果在一个value对象上调用pointer method,编译器会对原来的值做一份拷贝(参考函数传参规范),并在拷贝后的值上执行函数,那么如果函数有修改原receiver的值,则修改的行为都发生在拷贝的值上,而不会影响原值,这个错误很隐蔽不容易被调试发现,因此go决定放弃这个错误发生的可能性,直接不支持pointer method被value对象调用。(用心良苦呀,但是我喜欢这样,就是你连犯错误的可能性和机会都不给)
下面一段我就看不明白了,不知道value is addressable是什么意思。
There is a handy exception, though. When the value is addressable, the language takes care of the common case of invoking a pointer method on a value by inserting the address operator automatically.
回到我们的代码
因为m是一个值变量,而foo是一个pointer method,正好是前面分析的不支持的场景;那么如何更改,只要把调用Hello的时候取地址即可
func main() {
m := MyStruct { 10 }
// Hello(m)
Hello(&m)
fmt.Println(m.ii);
}
运行后我们得到
$ go build main.go && ./main
10
11
有疑问加站长微信联系(非本文作者)