Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 ● 牧内大輔 ● MakKi / @makki_d ● 所属:KLab株式会社 ○ このビルの22F ○ オンライン対戦の中継サーバを Goで ● gozxing ○ https://github.com/makiuchi-d/gozxing ○ 1D/2Dバーコードリーダーライブラリ ○ JavaのZXingをGoに移植

Slide 3

Slide 3 text

● 「とあるライブラリをGoに移植した結果…」 ○ 移植駆動学習 ○ カバレッジ100%を目指す 前回(golang.tokyo #19)のLT

Slide 4

Slide 4 text

今回のお話 ● 外部パッケージのstructの内部状態に依存した処理のテスト ○ 意図した内部状態をお手軽につくりたい ● こういうときどうする? ○ 非公開フィールドを公開しちゃう ■ 論外 ○ スタブ・モック ■ テストのために差し替え可能にする? ● gozxingでは元のZXingの構造を残す方針にやや反する ○ リフレクション ■ 内部構造に依存(密結合) ● 外部パッケージとはいえ同じライブラリ内なので許容 ■ 今回はこれで

Slide 5

Slide 5 text

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) ○ ... いけそうだ!

Slide 6

Slide 6 text

まずは試してみる 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) }

Slide 7

Slide 7 text

まずは試してみる 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

Slide 8

Slide 8 text

まずは試してみる ● Set*() は非公開フィールドを書き換えられない ● 正攻法では無理っぽい panic: reflect: reflect.Value.SetInt using value obtained using unexported field

Slide 9

Slide 9 text

ポインタを経由して上書きする方法 ● unsafe.Pointer ○ 任意の型のポインタにキャストできる ○ uintptrと相互変換 ■ reflectのValueからも取得できる ■ func (v Value) UnsafeAddr() uintptr ● 手順 a. 非公開フィールドのポインタ値( uintptr)を取得 b. uintptrをunsafe.Pointer方にキャスト c. 元と同じ型のポインタにキャスト d. 参照先に代入

Slide 10

Slide 10 text

やってみる 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) }

Slide 11

Slide 11 text

やってみる 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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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")

Slide 14

Slide 14 text

ポインタを経由して上書きする方法 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) }

Slide 15

Slide 15 text

ポインタを経由して上書きする方法 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}

Slide 16

Slide 16 text

まとめ パッケージの外から非公開フィールドを変更できた ● reflectとunsafeを組み合わせる ● 一歩間違えれば即panic ● あまりおすすめできない ● もっと良い方法があれば教えてください

Slide 17

Slide 17 text

No content