34
32

More than 5 years have passed since last update.

なぜ、Go言語にはpackageに対するリフレクションがないか。または如何にしてgo testが動作しているか。

Last updated at Posted at 2014-05-09

プログラマあるあるとしては、新しい言語をはじめると自然とリフレクション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.

というのがありました。
ふむふむ、なるほど。使っていない関数をリンクしていないと。

unused.c
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されます。

used.go
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にエントリがあることがわかります。

一方で、

unused.go
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

エントリが消えたことがわかります。
このように、利用されていない関数をリンクしない仕組みによって必要以上に実行ファイルが肥大化しないようにしているんですね。

34
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
32