Go1.15 からのreflectパッケージの挙動の違いについて
追記
Go1.15 からのreflectパッケージの挙動の違いについて | Zenn にも掲載してみました。
概要
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のようなチェック関数もないですし。