はじめに
any
型(interface{}
)に入っている構造体のポインタを取得しようとしてreflectパッケージのfunc (v Value) Addr() Value
メソッドを使ったところ、以下のエラーが発生しました。
panic: reflect.Value.Addr of unaddressable value
原因を調べたところaddressableという概念に出くわしました。
「アドレスでアクセス可能」であることを意味する単語ですが、正直これだけではよくわかりません。そこで、
- 「アドレスでアクセス可能」とは具体的にどういうことか
- addressableでない具体例2つ
を説明します。またこれらを踏まえてaddressableの定義を説明し、any型からポインタを取得するコードを紹介します。
アドレスでアクセス可能とは
addressableに関係するGo言語の一番重要な仕様は、代入だと思います。
addressableであることはGo言語における変数への代入の前提になっています。
var int x
x = 10
この例のようにx
に10
を代入できるのは、x
がaddressableだからです。
実際、
fmt.Println(&x) //0xc00009a0b0
のようにアドレスを取得でき、x
は「アドレスでアクセス可能」です。変数は言語仕様でaddressableと定義されていて、&
をつけてアドレスを取得できます。
これは使用しない値がメモリ上に残ることを避けるための仕様です。ポインタで参照できない場所に値を代入してもメリットはなく、またGC(Garbage Collection)の負担になってしまいます。
代入の他に構造体フィールドへのアクセスやインクリメント等、addressableを必要とする操作は多数あります。
では、addressableでないものとは、具体的にどういったケースでしょうか。
addressableでない例① 関数の返り値
package main
import "fmt"
type animal struct{}
func newAnimal() animal {
return animal{}
}
func main() {
fmt.Printf("%p\n", &newAnimal()) //invalid operation: cannot take address of newAnimal() (value of type animal)
}
animal構造体を作成する関数の戻り値に対してアドレスを取得しています。
関数の戻り値はaddressableでないのでビルドエラーが発生します。
func main() {
a := newAnimal()
fmt.Printf("%p\n", &a) //0xc00009c050
}
一度変数に代入することでエラーを回避できます。
addressableでない例② mapのインデックス指定
package main
type animal struct {}
func main() {
m := make(map[string]animal)
m["animal"] = animal{}
fmt.Printf("%p\n", &m["animal"]) //invalid operation: cannot take address of m["animal"] (map index expression of type animal)
}
sliceやarrayの要素がaddressableなのに対して、mapの要素はaddressableではありません。mapの要素に対してアドレスを取得するとビルドエラーが発生します。
mapの要素はaddressableではありませんが、例外的に
m["animal"] = animal{}
のように代入することができます。
func main() {
m := make(map[string]animal)
m["animal"] = animal{}
a := m["animal"]
fmt.Printf("%p\n", &a) //0xc000106050
}
こちらも、一度変数に代入することでエラーを回避できます。
addressableの定義
ここで、addressableなものを整理します。
言語仕様書のAddress_operatorsの章に、addressableの定義は以下のいずれかに該当すること、とあります。
- 変数(例:
var x
) - 間接演算子(例:
*x
) - スライスのインデックス指定(例:
s[10]
) - addressableな構造体のfield selector(例:
s.name
) - addressableなarrayのインデックス指定(例:
a[10]
)
これまでの例で説明した「関数の呼び出し」や「マップのインデックス指定」は、これらに該当しないのでaddressableではありません。
Addr()メソッドがパニックになる理由
冒頭の話題にもどりまして、Addr()メソッドを呼び出すにはreflect.Valueが、addressableである必要があります。
reflectパッケージの言うaddressableはreflectパッケージがアドレスを取得できるかどうかが基準です。
例えば、以下はaddressableではありません。
func main() {
num := 10
r := reflect.ValueOf(num)
fmt.Println(r.CanAddr()) //false
}
CanAddr()はreflect.Valueがaddressableかどうかを判定するメソッドです。
num
変数は&num
のようにアドレスを取得可能のはずですが、CanAddr()の返り値はfalseになってしまっています。
これは、reflectパッケージがnumのコピーの値を保持するだけで、アドレスに関する情報は抜け落ちてしまうからです。
このnumをaddressableにするにはポインタでreflectパッケージに渡し、その後Elem()
メソッドを呼び出してポインタが指す値を取得する必要があります。
func main() {
num := 10
r := reflect.ValueOf(&num).Elem()
fmt.Println(r.CanAddr()) //true
}
そうすることでreflect.Valueの値が元のポインタと紐づき、addressableなものとして扱うことができます。
any型に入っている構造体のポインタを取得する方法
func getStructPointer(v any) reflect.Value {
r := reflect.ValueOf(v)
if r.CanAddr() {
return r.Addr()
}
ptrType := reflect.PointerTo(r.Type())
ptr := reflect.New(ptrType.Elem())
ptr.Elem().Set(r)
return ptr
}
any型の値から構造体のポインタを取得する関数です。
if r.CanAddr() {
return r.Addr()
}
reflect.Valueがaddressableだった場合、直接Addr()関数を使ってポインタを取得することができます。
ptrType := reflect.PointerTo(r.Type())
構造体のポインタ型を取得します。この処理によって、構造体の型がわからなくても動的にポインタを取得できます。
ptr := reflect.New(ptrType.Elem())
ptr.Elem().Set(r)
ポインタのインスタンスを作成し、引数の値をセットします。
複雑になりましたが、以上の処理でany型の変数に入っている構造体のポインタを取得できます。
あとがき
addressableについて説明しているサイトが少なくて苦労したので作成しました。一見シンプルな概念ですが、定義が複雑だったり、何に使うのかがわかりずらかったりと、地味にひっかかるポイントが多いと思います。この記事が、addressable理解の一助になれば幸いです。