Go 方法選擇器的正規化

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

本文首發在微信 Go 101 公衆號,歡迎各位轉載本文。Go 101 公衆號將不定期發表一些原創短文,包含 Go 語言中的事實、細節和技巧等。有意關注者請掃描下面的二維碼。

關於更多 Go 語言編程中的事實、細節和技巧,請訪問《Go 語言 101》官方網站 https://gfw.go101.org (可點擊下面的原文鏈接直接訪問)或者項目地址 https://github.com/golang101/golang101。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/CP7Kqe2nS4p14xlOhD3zLQ