init関数のふしぎ #golang
December 5, 2016
この記事はQiitaの記事をエクスポートしたものです。内容が古くなっている可能性があります。
はじめに
知られているようで、案外知られてないことをQiitaに投稿していくのも良いと思い、軽いネタですがinit
関数についてまとめたいと思います。
init関数とは
init
関数は特殊な関数で、パッケージの初期化に使われます。
以下のようにmain
パッケージに書くとmain
関数より先に実行されます。
main
パッケージでない場合は、import
するだけで呼び出されます。
package main
import (
"fmt"
)
func init() {
fmt.Println("hello, init")
}
func main() {
fmt.Println("Hello, main")
}
init関数は何に使うのか?
上記では、初期化に使うと書きました。名前からしてそれは想像できますよね。
init
関数は基本的にはパッケージ変数の初期化に用いられます。なぜならば、何かしらの副作用をパッケージに変数に与えなければ、呼んでも意味がないからです。もちろん、外部のリソースに何かしらの副作用を与える場合(サーバに何かリクエストしたり、ファイルに書き込んだり)もありますが、基本的にはパッケージ変数の初期化に使われます。
パッケージ変数は、代入文でも初期化を行うことがありますが、以下のような場合にinit
関数を使います。
- 代入文で表現できない初期化(forを使うなど)
- もちろん、
init
関数でやらず、別の関数を呼び出してそれを代入してもよい
- もちろん、
- 初期化処理が複雑
- 他のパッケージのパッケージ変数を初期化してやる
image/png
やimage/jpeg
などでやっている
- main関数が使えない場合にエントリーポイントして使う
- GAE/Goはmain関数を作らず、
init
関数をエントリーポイントとして使用する
- GAE/Goはmain関数を作らず、
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")
}
言語仕様を読む限り、依存するパッケージがある場合(import文がある場合)には、それらのパッケージの初期化が終わった後に自身のパッケージのinit
関数が実行されるようです。
init関数はいくつ書けるのか?
よくよく考えると、複数ファイルにまたがって、1つのパッケージに複数のinit
関数を書いていることがあるのに気づくことがあります。他の関数の場合は、複数定義すると、もちろんコンパイルエラーになります。
ちなみに、ファイルに1つという決まりもなく、複数書けるようになっています。
package main
import (
"fmt"
)
func init() {
fmt.Print("hello")
}
func init() {
fmt.Println(", init")
}
func main() {
}
不思議ですね。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())
}
実行すると、以下のように出力されます。
main.init.1
main.init.2
main.main
どうやら、各init
関数には番号が付けられており、区別されているようです。
init関数は呼び出せるのか
init
関数は他の関数内から呼び出せるんでしょうか?
main
関数から呼び出してみます。
package main
import "fmt"
func init() {
fmt.Println("hoge")
}
func main() {
init()
}
実行すると、以下のようにエラーがでます。
tmp/sandbox671239315/main.go:10: undefined: init
定義されていないことになるようです。なるほど!
おわりに
init
関数についてまとめてみました。
ちなみに、この記事を書こうと思った元ネタはこちらです。これも面白いので、ぜひ読んでみて下さい!