🛠️
Go1.15 からのreflectパッケージの挙動の違いについて
自ブログからの転載です。
概要
Go1.15からreflectパッケージの一部挙動が変わった。これにより、Go1.14以前で動作していたコードがPanicしうる。
非公開構造体を埋め込み、その構造体のメソッドをCallした場合にPanicする。
リリースノートの記述
Go 1.15のリリースノートによると、reflectパッケージについて下記記述がありました。
Package reflect now disallows accessing methods of all non-exported fields, whereas previously it allowed accessing those of non-exported, embedded fields. Code that relies on the previous behavior should be updated to instead access the corresponding promoted method of the enclosing variable.
サンプルコード
サンプルコードは下記です。二つの構造体を定義しています。
- 非公開の
unexportedStruct
Printするだけのメソッドを持つ。 - 公開の
R
上記unexportedStructが埋め込まれている。
Rを用いて、unexportedStruct内のメソッドにアクセスしようとするコードです。エラー処理は省いています。
package main
import (
"fmt"
"reflect"
)
type unexportedStruct struct{}
func (s unexportedStruct) Print() {
fmt.Println("Print!")
}
type R struct {
unexportedStruct
}
func main() {
v := reflect.ValueOf(R{})
fmt.Printf("NumMethod=%d\n", v.Field(0).NumMethod())
method := v.Field(0).Method(0)
fmt.Printf("Method=%v\n", method)
method.Call(nil)
}
Go1.14とGo1.15の違い
下記が挙動の違いです。Go 1.14.7 ではPrint関数が実行されるのに対し、Go1.15ではPanicして落ちます。
Go 1.15
$ go version
go version go1.15 linux/amd64
$ go run unexported.go
NumMethod=1
Method=0x48f340
panic: reflect: reflect.Value.Call using value obtained using unexported field
goroutine 1 [running]:
reflect.flag.mustBeExportedSlow(0x2b3)
/usr/local/go/src/reflect/value.go:237 +0x131
reflect.flag.mustBeExported(...)
/usr/local/go/src/reflect/value.go:228
reflect.Value.Call(0x4dd980, 0x5daf48, 0x2b3, 0x0, 0x0, 0x0, 0x1, 0x10, 0x0)
/usr/local/go/src/reflect/value.go:335 +0x52
main.main()
/home/taka/tmp/unexported.go:22 +0x1ef
exit status 2
Go 1.14
$ go version
go version go1.14.7 linux/amd64
$ go run unexported.go
NumMethod=1
Method=0x488b20
Print!
修正コミット
この辺ですかね。
所感
Go1.14までの挙動がバグっぽいので適切に修正されたのかなと。
けれどもユーザーとしてはPanicするか否かをどう判別するのがいいんでしょうかね。いずれもNumMethod()の返り値は同じですし、CanCallのようなチェック関数もないですし。
Discussion