Skip to content

Goで関数が同一かどうか調べる

Goの仕様として関数同士の比較はできないことはドキュメント等にも明記されているけど、何か抜け道はないのか。調べたが、Go 1.22時点ではどうやってもジェネリックな関数を安全に比較できない。

一般的な関数やメソッドの場合

Section titled “一般的な関数やメソッドの場合”

普通の関数やメソッドであれば、reflectruntimeを使えば関数アドレスで比較できる。

方法はいくつかある。

reflect.ValueOf(f).Pointer()
uintptr(reflect.ValueOf(f).UnsafePointer())

どちらを使えばいいのかについては、reflect.Pointerのドキュメントに

It’s preferred to use uintptr(Value.UnsafePointer()) to get the equivalent result.

とある。

runtime.FuncForPCでメモリアドレスから関数の情報(runtime.Func)を取得できる。

runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Entry()

名前を使ってもだいたい一致させることができる。

func main() {
printFunc(time.Now) // time.Now
printFunc(user.Current) // os/user.Current
printFunc((*http.Client).Do) // net/http.(*Client).Do
printFunc(rand.N[int]) // math/rand/v2.N[...]
printFunc(pointer.Int) // github.com/lufia/go-pointer.Int
printFunc((*backoff.Backoff).Wait)} // github.com/lufia/backoff.(*Backoff).Wait
func printFunc[T any](fn T) {
v := reflect.ValueOf(fn)
p := v.UnsafePointer()
f := runtime.FuncForPC(uintptr(p))
fmt.Println(f.Name())
}

型パラメータを持つ関数の場合

Section titled “型パラメータを持つ関数の場合”

ジェネリックな関数の場合、Goの関数アドレスは型パラメータ毎に別の型として扱われるので、上記の方法では型パラメータごとに異なるアドレスを取得してしまって関数アドレスによる比較が失敗する。