再议go语言的value receiver和pointer receiver

CodeGeek · · 1240 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

再议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.

解释一下就是

  1. 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
  1. 原因是: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

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:CodeGeek

查看原文:再议go语言的value receiver和pointer receiver

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1240 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传