Go 允许一些选择器的简化形式。
例如,在下面这个程序中,t1.M1
是 (*t1).M1
的简化形式,而 t2.M2
则是 (&t2).M2
的简化形式。
在编译时,编译器将把简化的形式正规化为它们原来各自的完整形式。
下面这个程序打印出 0
和 9
,因为对 t1.X
的修改对 (*t1).M1
的估值结果没有影响。
package main
type T struct {
X int
}
func (t T) M1() int {
return t.X
}
func (t *T) M2() int {
return t.X
}
func main() {
var t1 = new(T)
var f1 = t1.M1 // <=> (*t1).M1
t1.X = 9
println(f1()) // 0
var t2 T
var f2 = t2.M2 // <=> (&t2).M2
t2.X = 9
println(f2()) // 9
}
在下面的代码中,函数 foo
运行正常,但函数 bar
会产生恐慌。
原因是 s.M
是 (*s.T).M
的简化形式。
在编译时,编译器会将此简化形式规范化为原来的完整形式。
在运行时,如果 s.T
是 nil,那么对 *s.T
的估值将导致一个恐慌。
对 s.T
的两次修改对 *s.T
的估值结果没有影响。
package main
type T struct {
X int
}
func (t T) M() int {
return t.X
}
type S struct {
*T
}
func foo() {
var s = S{T: new(T)}
var f = s.M // <=> (*s.T).M
s.T = nil
f()
}
func bar() {
var s S
var f = s.M // panic
s.T = new(T)
f()
}
func main() {
foo()
bar()
}
请注意,接口方法值和通过反射得到的方法值将被延迟扩展为提升的方法值。
例如,在下面的程序中,对 s.T.X
的修改对通过反射和接口方式得到的方法值的返回值有影响。
package main
import "reflect"
type T struct {
X int
}
func (t T) M() int {
return t.X
}
type S struct {
*T
}
func main() {
var s = S{T: new(T)}
var f = s.M // <=> (*s.T).M
var g = reflect.ValueOf(&s).Elem().
MethodByName("M").
Interface().(func() int)
var h = interface{M() int}(s).M
s.T.X = 3
println( f() ) // 0
println( g() ) // 3
println( h() ) // 3
}
来源:https://github.com/golang/go/issues/47863
但是,在当前版本(1.18 版本)的官方标准 Go 编译器的实现中存在一个 bug。
官方标准 Go 编译器中一个优化会将一些接口方法值过度去虚拟化(de-virtualization),从而导致不正确的结果。
比如,下面这个程序应该打印出 2 2
,但是目前它却打印出 1 2
。
package main
type I interface{ M() }
type T struct{
x int
}
func (t T) M() {
println(t.x)
}
func main() {
var t = &T{x: 1}
var i I = t
var f = i.M
defer f() // 2(正确)
// i.M 将在编译时刻被(错误地)去虚拟化为 (*t).M。
defer i.M() // 1(错误)
t.x = 2
}
目前尚不清楚此 bug 何时将被修复。
来源:https://github.com/golang/go/issues/52072
推荐阅读