1. tenntenn

    Posted

    tenntenn
Changes in title
+init関数のふしぎ #golang
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,174 @@
+## はじめに
+
+知られているようで、案外知られてないことをQiitaに投稿していくのも良いと思い、軽いネタですが`init`関数についてまとめたいと思います。
+
+## init関数とは
+
+`init`関数は特殊な関数で、パッケージの初期化に使われます。
+以下のように`main`パッケージに書くと`main`関数より先に実行されます。
+`main`パッケージでない場合は、`import`するだけで呼び出されます。
+
+```
+package main
+
+import (
+ "fmt"
+)
+
+func init() {
+ fmt.Println("hello, init")
+}
+
+func main() {
+ fmt.Println("Hello, main")
+}
+```
+[Playgroundで動かす](https://play.golang.org/p/Nsbs7YIbWp)
+
+## init関数は何に使うのか?
+
+上記では、初期化に使うと書きました。名前からしてそれは想像できますよね。
+`init`関数は基本的にはパッケージ変数の初期化に用いられます。なぜならば、何かしらの副作用をパッケージに変数に与えなければ、呼んでも意味がないからです。もちろん、外部のリソースに何かしらの副作用を与える場合(サーバに何かリクエストしたり、ファイルに書き込んだり)もありますが、基本的にはパッケージ変数の初期化に使われます。
+
+パッケージ変数は、代入文でも初期化を行うことがありますが、以下のような場合に`init`関数を使います。
+
+* 代入文で表現できない初期化(forを使うなど)
+ * もちろん、`init`関数でやらず、別の関数を呼び出してそれを代入してもよい
+* 初期化処理が複雑
+* 他のパッケージのパッケージ変数を初期化してやる
+ * `image/png`や`image/jpeg`などでやっている
+* main関数が使えない場合にエントリーポイントして使う
+ * GAE/Goはmain関数を作らず、`init`関数をエントリーポイントとして使用する
+
+## init関数はいつ呼ばれるのか
+
+以下のコードを実行すると、出力はどうなるでしょうか?
+`Hello, playground`と出るのはなんとなく空気を読めばわかりますが、よく見るとパッケージ変数の初期化の方が先に実行されていることが分かります。
+また、もちろん`main`関数より先に実行されています。
+
+```
+package main
+
+import (
+ "fmt"
+)
+
+var msg = message()
+
+func message() string {
+ return "Hello"
+}
+
+func init() {
+ fmt.Print(msg)
+}
+
+func main() {
+ fmt.Println(", playground")
+}
+```
+[Playgroundで動かす](https://play.golang.org/p/_2pIMF9C9i)
+
+[言語仕様](https://golang.org/ref/spec#Package_initialization)を読む限り、依存するパッケージがある場合(import文がある場合)には、それらのパッケージの初期化が終わった後に自身のパッケージの`init`関数が実行されるようです。
+
+## init関数はいくつ書けるのか?
+
+よくよく考えると、複数ファイルにまたがって、1つのパッケージに複数の`init`関数を書いていることがあるのに気づくことがあります。他の関数の場合は、複数定義すると、もちろんコンパイルエラーになります。
+
+ちなみに、ファイルに1つという決まりもなく、複数書けるようになっています。
+
+```
+package main
+
+import (
+ "fmt"
+)
+
+func init() {
+ fmt.Print("hello")
+}
+
+func init() {
+ fmt.Println(", init")
+}
+
+func main() {
+}
+```
+[Playgroundで動かす](https://play.golang.org/p/ZaBbdTcHLY)
+
+不思議ですね。`runtime`パッケージを使って関数名がどうなっているのか調べてみましょう。
+
+```
+package main
+
+import (
+ "fmt"
+ "runtime"
+)
+
+func init() {
+ var pcs [1]uintptr
+ runtime.Callers(1, pcs[:])
+ fn := runtime.FuncForPC(pcs[0])
+ fmt.Println(fn.Name())
+}
+
+func init() {
+ var pcs [1]uintptr
+ runtime.Callers(1, pcs[:])
+ fn := runtime.FuncForPC(pcs[0])
+ fmt.Println(fn.Name())
+}
+
+func main() {
+ var pcs [1]uintptr
+ runtime.Callers(1, pcs[:])
+ fn := runtime.FuncForPC(pcs[0])
+ fmt.Println(fn.Name())
+}
+```
+[Playgroundで動かす](https://play.golang.org/p/4r1-rD0yE9)
+
+実行すると、以下のように出力されます。
+
+```
+main.init.1
+main.init.2
+main.main
+```
+
+どうやら、各`init`関数には番号が付けられており、区別されているようです。
+
+## init関数は呼び出せるのか
+
+`init`関数は他の関数内から呼び出せるんでしょうか?
+`main`関数から呼び出してみます。
+
+```
+package main
+
+import "fmt"
+
+func init() {
+ fmt.Println("hoge")
+}
+
+func main() {
+ init()
+}
+```
+[Playgroundで動かす](https://play.golang.org/p/9agxBo8o4Q)
+
+実行すると、以下のようにエラーがでます。
+
+```
+tmp/sandbox671239315/main.go:10: undefined: init
+```
+
+定義されていないことになるようです。なるほど!
+
+## まとめ
+
+`init`関数についてまとめてみました。
+ちなみに、この記事を書こうと思った元ネタは[こちら](https://talks.godoc.org/github.com/davecheney/presentations/gopher-puzzlers.slide)です。これも面白いので、ぜひ読んでみて下さい!