Goで構造体の非公開フィールドにアクセスする方法
Go の 構造体 (struct) におけるフィールドは、フィールド名が小文字始まりであれば 非公開フィールド (unexported field) となり、パッケージ外からアクセスすることができません(参考: Exported identifiers - The Go Programming Language Specification)。 組織内で管理しているソースコードなら単に修正してしまえばよいのですが、外部のライブラリなどの場合、変更してもらうのは大変です。
このような、やむを得ない理由で非公開フィールドを参照したい場合、ちょっとした工夫が必要になります。 試してみたところ、以下の2つの方法で非公開フィールドを取り出すことができました。
reflect.ValueOf
とunsafe.Pointer
を使う方法go.mod
でモジュールを replace する方法
1. reflect.ValueOf
と unsafe.Pointer
を使う方法
以下のようにして reflect.ValueOf
と unsafe.Pointer
を併用することで、非公開フィールドにアクセスすることができます。
このソースコードは、外部パッケージ outside
の構造体 OuterStruct
に定義された privateStruct
などの非公開フィールドを取り出したい、という設定です。
同じく OuterStruct
に定義された、 int
型の privateInt
や、 string
型の privateString
などの非公開フィールドの読み取り・書き込みもできました。
package main import ( "fmt" "reflect" "unsafe" "github.com/takuyaa/go-private-test/outside" ) func main() { var outer *outside.OuterStruct = outside.NewOuterStruct() // var v reflect.Value = reflect.ValueOf(*outer) としてもよいが、コピーが走ってしまうため、もとの outer は変更できないし、効率も悪い var v reflect.Value = reflect.ValueOf(outer).Elem() fmt.Printf("v: %#v\n", v) // v: outside.OuterStruct{privateInt:10, privateString:"dog", privateStruct:(*outside.InnerStruct)(0xc0000b4030)} // privateStruct を取得 var psv reflect.Value = v.FieldByName("privateStruct").Elem() fmt.Printf("psv: %#v\n", psv) // psv: outside.InnerStruct{Message:"Hi!"} // 直接は中身を取れない // is := sv.Interface() // panic: reflect.Value.Interface: cannot return value obtained from unexported field or method var is *outside.InnerStruct = (*outside.InnerStruct)(unsafe.Pointer(psv.UnsafeAddr())) // unsafe.Pointer を使うと中身を取れる fmt.Printf("is: %#v\n", is) // is: &outside.InnerStruct{Message:"Hi!"} // int 型の private フィールドの取得と変更 var piv reflect.Value = v.FieldByName("privateInt") pi := (*int)(unsafe.Pointer(piv.UnsafeAddr())) fmt.Printf("pi: %#v\n", *pi) // pi: 10 *pi = 111 // フィールドの値を変更できる fmt.Printf("pi: %#v\n", *pi) // pi: 111 // string 型の private フィールドの取得と変更 var pstrv reflect.Value = v.FieldByName("privateString") ps := (*string)(unsafe.Pointer(pstrv.UnsafeAddr())) fmt.Printf("ps: %#v\n", *ps) // ps: "dog" *ps = "cat" // フィールドの値を変更できる fmt.Printf("ps: %#v\n", *ps) // ps: "cat" // PublicMethod の呼び出し pubm := reflect.ValueOf(outer).MethodByName("PublicMethod") // Elem() 経由ではなくポインタからアクセス fmt.Printf("pubm: %#v\n", pubm) // pubm: (func())(0x1082580) pubm.Call(nil) // Hi! // privateMethod の呼び出しは失敗する prim := reflect.ValueOf(outer).MethodByName("privateMethod") // Elem() 経由ではなくポインタからアクセス fmt.Printf("prim: %#v\n", prim) // prim: <invalid reflect.Value> // prim.Call(nil) // panic: reflect: call of reflect.Value.Call on zero Value } -- go.mod -- module github.com/takuyaa/go-private-test go 1.13 -- outside/lib.go -- package outside type InnerStruct struct { Message string } type OuterStruct struct { privateInt int privateString string privateStruct *InnerStruct } func NewOuterStruct() *OuterStruct { return &OuterStruct{ privateInt: 10, privateString: "dog", privateStruct: &InnerStruct{Message: "Hi!"}, } } func (s *OuterStruct) privateMethod() { println(s.privateStruct.Message) } func (s *OuterStruct) PublicMethod() { println(s.privateStruct.Message) }
上記のソースコードを The Go Playground にコピペするか、以下の URL でも試せます*1:
https://play.golang.org/p/iomnJeRjOns
Pros
- 既存のコードには手を入れずに、最小限の追加実装で非公開フィールドにアクセス可能
- 2, 3 行のコードで非公開フィールドへのポインタが取得できる
Cons
reflect
やunsafe
を使うので、プロダクションのコードでは採用しづらい- パフォーマンス上の問題も起きうる
- あくまでテストやデバッグで使うのに留めるのが無難そう
- この方法では非公開メソッドへのアクセスはできない(後述)
非公開メソッドの呼び出しは難しい
上で見たように、非公開メソッドは MethodByName
で reflect.Value
を取得しても、 invalid reflect.Value
となり、それを通して関数呼び出しをすることはできませんでした。
spance/go-callprivate もダメ元で試してみましたが、残念ながらうまくいきませんでした。((プライベートメソッドの場合、 reflect.Value
を取得してもそれが invalid なので、 private.SetAccessible(method)
が失敗します。))
alangpierce/go-forceexport も time.now
のような、レシーバではない関数を呼び出すためのもののようで、用途が異なるようです(参考: how to call a unexported Func in a struct? · Issue #1 · alangpierce/go-forceexport)。
2. go.mod
でモジュールを replace する方法
2 つ目の方法は、 Go Modules の replace
の機能を使う方法です*2。
- 対象となる外部モジュールのソースコードを取得し、利用側のリポジトリに置く
- 以下では、
github.com/outside/module
というモジュールをプロジェクトルートの./outside/module/
に置いたとして説明します
- 以下では、
- そのモジュールに
go.mod
がなければgo mod init
で作成しておく - 利用側の
go.mod
に、以下のようなreplace
ディレクティブを追記するreplace github.com/outside/module => ./outside/module
- 手元のモジュールを修正し、非公開フィールドを返す外部メソッドを生やすなり自由に改造する*3
この方法なら、非公開メソッドの呼び出しも可能になります。
たとえば、 func (s *OuterStruct) privateMethod
という非公開メソッドがあったとき、それを呼び出すだけの公開メソッド func (s *OuterStruct) CallPrivateMethod
を定義し、利用側からはそれを呼ぶようにすれば、最小限の変更で非公開メソッドを呼ぶことができます。
Pros
- 非公開メソッドも呼べるなど、自由度が高い
panic
やプリントデバッグなどの命令を仕込んだりもできる