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 何时将被修复。