Go のリフレクション小ネタ
これは何?
Go の reflect
パッケージを使って DI ライブラリを作ろうとしたときに知った小ネタです。
この記事の内容は Go 1.24 で実施しています。
構造体の型引数の型名を取得する方法
いきなりですが、reflect.Type
から直接型引数を取得することはできないようです。proposal は出ているようですが、進んではいなそうでした。
ですので、型引数を取得するためには reflect.Type
の Name()
メソッドを使って型名を取得し、文字列をパースする必要があります。
package main
import (
"fmt"
"reflect"
)
type Foo[T1, T2, T3, T4 any] struct{}
type Bar struct{}
func main() {
fmt.Println(reflect.TypeOf(Foo[Bar, *Bar, []Bar, int]{}).Name()) // Foo[main.Bar,*main.Bar,[]main.Bar,int]
}
型名の []
の部分に ,
区切りで型名が入り、それぞれ {パッケージのパス}.{型名}
という形式になっています。ポインタの場合はパッケージのパスの前に *
、配列の場合は []
がつきます。また、組込みデータ型はパッケージのパスはありません。
ちなみに、無名の構造体を使った場合は、定義全てが型名に含まれます。
package main
import (
"fmt"
"reflect"
)
type Foo[T any] struct{}
func main() {
fmt.Println(reflect.TypeOf(Foo[struct{ Name string }]{}).Name()) // Foo[struct { Name string }]
}
インターフェースの reflect.Type を取得する方法
インターフェースの型を取得する場合、以下のように直接 reflect.TypeOf
を使うと nil
になってしまいます。
package main
import (
"fmt"
"reflect"
)
type Foo interface {
}
func main() {
var f Foo
fmt.Println(reflect.TypeOf(f).Name()) // panic: runtime error: invalid memory address or nil pointer dereference
}
インターフェースの型を取得する場合は、ポインタに対して reflect.TypeOf
を使って .Elem()
で実体を取り出す必要があります。
package main
import (
"fmt"
"reflect"
)
type Foo interface {
}
func main() {
var f Foo
fmt.Println(reflect.TypeOf(&f).Elem().Name()) // Foo
}
関数の型引数の型名は取得できない
まず、 関数名は runtime.FuncForPC
を使って取得できます。
package main
import (
"fmt"
"reflect"
"runtime"
)
func Foo() {
}
func main() {
fmt.Println((runtime.FuncForPC(reflect.ValueOf(Foo).Pointer()).Name())) // main.Foo
}
型引数を持つ関数に対して同様の方法で関数名を取得しようとすると、型引数の部分は [...]
という形式になってしまい取得できません。
package main
import (
"fmt"
"reflect"
"runtime"
)
func Foo[T any]() {}
func main() {
fmt.Println((runtime.FuncForPC(reflect.ValueOf(Foo[int]).Pointer()).Name())) // main.Foo[...]
}
これについては go/ast
等をつかってコード解析を行うしか取得する方法はなさそうでした。
まとめ
Go のリフレクションの小ネタでした。軽く調べて見つからなかったネタに絞ったら思ったより数が少なめでした...
最後に現在作成中のDIライブラリのリンクを貼っておきます。興味があれば見てみてください。
Discussion