一个隐藏在方法集和方法调用中且易被忽略的小细节
来自公众号:新世界杂货铺
作为一个长期从事 Go 语言开发的程序猿,笔者不敢说自己是老油条但也勉强算一个小油条。然而就在今天,笔者研究 TLS/SSL 握手源码的时候,突然灵光一闪,想到了一个和自己认知不符的现象,于是赶紧写了一个例子验证一番,结果当头一棒直到码这篇文章时依旧懵逼。
话不多说,上锤!
不好意思,不是这个锤,是下面这个:
正确答案笔者就不直接公布了,请各位读者耐心在后文寻找答案。
方法集
根据 golang 官方文档知道,一个类型有一个与之关联的方法集。接口类型的方法集是接口中定义的方法。
官方文档中特别提到,类型 T 的方法集包含用 T 声明为 Receiver 的所有方法,而指针类型*T 的方法集包含用 T 和*T 声明的所有方法。
此时,我们回到上面的例子可以很明显的知道下面这段代码输出为false true
。
T 类型的方法集不包含*T 类型的方法集,因此 t1 无法转为set
接口类型。
事实上,根据这部分官方文档笔者更加疑惑了,因为上述例子可以正常运行,而且类型为test
的变量调用了(*test).set1
方法。抱着这样的疑惑笔者疯狂谷狗,最后在 stackoverflow 的指导下发现了这种情况和方法调用有关。
这里特别感谢一下谷狗和 stackoverflow。
方法调用
方法调用笔者在这里仅说明和本篇相关的内容,其他细节相信各位读者都已经了然于胸。
下面,先看看官方文档原文:
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()
简单来说,如果 x 可寻址,且 &x 的方法集包含 m,则x.m()
是(&x).m
的缩写。这样前面的例子能够正常运行也在情理之中了。
因此,前面例子的最终输出结果是:1133 false true
。
如果读者对结构体中的值未发生改变有疑惑,请参考笔者的这篇文章——为什么go中的receiver name不推荐使用this或者self。
你以为你都懂了
写完前面的方法集和方法调用笔者细细思考一番,确认没有其他遗漏的细节,于是放心的上了个厕所。结果厕所还没上完,立马想到一个问题(额外多扯一句,笔者经常在上厕所的时候找到灵感,这可能就是劳逸结合的最佳实践吧):
如果你对上面的代码没有任何疑问且认为上述代码能够正常运行,那只能说明你对本文的阅读还不够认真。
我们先看看上述代码在 vscode 中的报错。
前面介绍方法调用时,如果 x 可寻址,则 x 可以调用 &x 的类型的方法集。上述代码s3
是常量,是不可以寻址的,因此无法调用(*los).p2
方法。
以上,就是笔者曾经忽略的细节,现在回过头来看一看倒也充满了乐趣。
彩蛋
本篇是研究 TLS/SSL 握手流程的副产品,因为 TLS/SSL 握手流程笔者还在整理中,故这篇文章先行一步给个预告,下一期 TLS/SSL 握手流程敬请期待。
最后,衷心希望本文能够对各位读者有一定的帮助。
注:
1. 写本文时, 笔者所用 go 版本为: go1.15.2
2. 文章中所用完整例子:https://github.com/Isites/go-coder/blob/master/receiver/main.go
参考
https://golang.org/ref/spec#Method_sets
https://golang.org/ref/spec#Calls
版权声明: 本文为 InfoQ 作者【Gopher指北】的原创文章。
原文链接:【http://xie.infoq.cn/article/2f9b7bdff4e79c3f89110bf67】。未经作者许可,禁止转载。
评论