プログラマあるあるとしては、新しい言語をはじめると自然とリフレクションAPIを探してしまうものです。
当然、golangにもreflectパッケージはあって、それを眺める訳ですが、packageに自体に対するclassや関数の列挙がないのです。
そうなってくると不思議なのは、go test
です。
go test
は、テストコードから、TestやBench、Exampleといったprefixのついているものだけを実行したり、Testしたい関数のprefixを指定できたりします。
$ go test .
$ go test -bench Unit
これをみて、すっかりLL脳になっていた私は、packageに対してのreflectionがあるんだろうと思い込んでいたのです。
実際のところgo test
は標準ライブラリとしてついてくるgo/parserをつかい、astから関数を見つけ出し、mainパッケージやmain関数をtext/templateをつかって作成し、コンパイルするという方法をとっていました。
一方、すべて静的リンクされているはずなのだから、ソースコードなど持たなくてもpackageから関数を取り出す方法があるだろう、といろいろ探してみると
このようなStack Overflowの記事を見つけました。
なんのことはない動的に名前から関数を見つけたいという質問なんですが、
それに対する回答で、
There's no way to dynamically look up a function by name, but I think it's worth mentioning why. Basically, the reason is so that the compiler and/or linker can eliminate unused functions.
Consider that if you were able to get a function by name, then every function in every imported package (recursively) would have to be linked into the final executable, even if it was never used, just in case someone wanted to look it up by name. People already complain about the large size of Go binaries, but this would cause them to be much larger still.
というのがありました。
ふむふむ、なるほど。使っていない関数をリンクしていないと。
int main(){
}
// 使っていない。
int AAAA(int a,int b){
return a+b;
}
たとえば、C言語ではこのようにつかっていない関数があった場合、
$ nm -a a.out
0000000100000f80 T _AAAA
0000000100000000 T __mh_execute_header
0000000100000f70 T _main
U dyld_stub_binder
こんなかんじにAAAAもlinkされます。
package main
import (
"fmt"
)
func main() {
AAAAAA(2, 4)
}
func AAAAAA(a, b int) {
fmt.Println(a + b)
}
go言語でも次のように使っている関数であれば、
$ nm -a hoge | grep main
207:00000000000226a0 T _main
1692:0000000000002040 t main.AAAAAA
:
:
mainにエントリがあることがわかります。
一方で、
package main
import (
"fmt"
)
func main() {
// AAAAAA(2, 4)
}
func AAAAAA(a, b int) {
fmt.Println(a + b)
}
このようにコメントアウトして、使っていない関数を作ると
nm hoge | grep main
206:0000000000022310 T _main
1677:00000000000c6e8c s main.gcargs·0
1678:00000000000c6e90 s main.gcargs·2
1679:00000000000c6e94 s main.gclocals·0
1680:00000000000c6e98 s main.gclocals·2
1681:0000000000002010 t main.init
1682:000000000016d9c2 s main.initdone·
1683:0000000000002000 t main.main
エントリが消えたことがわかります。
このように、利用されていない関数をリンクしない仕組みによって必要以上に実行ファイルが肥大化しないようにしているんですね。