パッケージ外から非公開フィールドを変更する方法.pdf

99b72ba4c7dd4da957edb3e619a6d71f?s=47 MakKi
December 18, 2018
78

 パッケージ外から非公開フィールドを変更する方法.pdf

golang.tokyo #20

99b72ba4c7dd4da957edb3e619a6d71f?s=128

MakKi

December 18, 2018
Tweet

Transcript

  1. パッケージ外から 非公開フィールドを 変更する方法 golang.tokyo #20 LT 牧内大輔

  2. 自己紹介 • 牧内大輔 • MakKi / @makki_d • 所属:KLab株式会社 ◦

    このビルの22F ◦ オンライン対戦の中継サーバを Goで • gozxing ◦ https://github.com/makiuchi-d/gozxing ◦ 1D/2Dバーコードリーダーライブラリ ◦ JavaのZXingをGoに移植
  3. • 「とあるライブラリをGoに移植した結果…」 ◦ 移植駆動学習 ◦ カバレッジ100%を目指す 前回(golang.tokyo #19)のLT

  4. 今回のお話 • 外部パッケージのstructの内部状態に依存した処理のテスト ◦ 意図した内部状態をお手軽につくりたい • こういうときどうする? ◦ 非公開フィールドを公開しちゃう ▪

    論外 ◦ スタブ・モック ▪ テストのために差し替え可能にする? • gozxingでは元のZXingの構造を残す方針にやや反する ◦ リフレクション ▪ 内部構造に依存(密結合) • 外部パッケージとはいえ同じライブラリ内なので許容 ▪ 今回はこれで
  5. Goのリフレクション • 標準パッケージ “reflect” ◦ https://golang.org/pkg/reflect/ • Value型 ◦ オブジェクトの型と値を持ち、操作するためのもの

    ◦ func ValueOf(i interface{}) Value ◦ func (v Value) FieldByName(name string) Value ◦ func (v Value) Set(x Value) ◦ func (v Value) SetBool(x bool) ◦ ... いけそうだ!
  6. まずは試してみる package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") v.SetInt(100) fmt.Println(a) }
  7. まずは試してみる package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") v.SetInt(100) fmt.Println(a) } panic: reflect: reflect.Value.SetInt using value obtained using unexported field
  8. まずは試してみる • Set*() は非公開フィールドを書き換えられない • 正攻法では無理っぽい panic: reflect: reflect.Value.SetInt using

    value obtained using unexported field
  9. ポインタを経由して上書きする方法 • unsafe.Pointer ◦ 任意の型のポインタにキャストできる ◦ uintptrと相互変換 ▪ reflectのValueからも取得できる ▪

    func (v Value) UnsafeAddr() uintptr • 手順 a. 非公開フィールドのポインタ値( uintptr)を取得 b. uintptrをunsafe.Pointer方にキャスト c. 元と同じ型のポインタにキャスト d. 参照先に代入
  10. やってみる package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") p := unsafe.Pointer(v.UnsafeAddr()) ptr := (*int)(p) *ptr = 100 fmt.Println(a) }
  11. やってみる package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") p := unsafe.Pointer(v.UnsafeAddr()) ptr := (*int)(p) *ptr = 100 fmt.Println(a) } panic: reflect.Value.UnsafeAddr of unaddressable value
  12. unaddressable value • ValueOf関数へ値渡ししている ◦ 一時的なコピーが作られる ◦ 戻り値のValueが指すのはコピーの方 • コピーのアドレスは取れない

    ◦ The Laws of Reflection (The Go Blog) ▪ https://blog.golang.org/laws-of-reflection ◦ Settabilityの説明だけどAddressabilityも似た感じ a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") A a A a 変数 a FieldByName の戻り値 ValueOf の引数 v ValueOf の戻り値 コピー unaddressable addressable
  13. ValueOfにポインタを渡す • 引数としてコピーされるのはポインタ ◦ 参照先の実体は同じもの • Elem()でポインタ参照先のValueを取得 ◦ Valueが対応するのは変数 aと同じもの

    ◦ コピーではないので Addressable • 戻り値 v もAddressable A a 変数 a FieldByName の戻り値 ValueOf の引数 v ValueOf の戻り値 コピー &a Elem の戻り値 unaddressable addressable a := sub.NewA() v := reflect.ValueOf(&a).Elem().FieldByName("a")
  14. ポインタを経由して上書きする方法 package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(&a).Elem().FieldByName("a") p := unsafe.Pointer(v.UnsafeAddr()) ptr := (*int)(p) *ptr = 100 fmt.Println(a) }
  15. ポインタを経由して上書きする方法 package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(&a).Elem().FieldByName("a") p := unsafe.Pointer(v.UnsafeAddr()) ptr := (*int)(p) *ptr = 100 fmt.Println(a) } {100}
  16. まとめ パッケージの外から非公開フィールドを変更できた • reflectとunsafeを組み合わせる • 一歩間違えれば即panic • あまりおすすめできない • もっと良い方法があれば教えてください

  17. None