Edited at

init関数のふしぎ #golang


はじめに

知られているようで、案外知られてないことを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で動かす


init関数は何に使うのか?

上記では、初期化に使うと書きました。名前からしてそれは想像できますよね。

init関数は基本的にはパッケージ変数の初期化に用いられます。なぜならば、何かしらの副作用をパッケージに変数に与えなければ、呼んでも意味がないからです。もちろん、外部のリソースに何かしらの副作用を与える場合(サーバに何かリクエストしたり、ファイルに書き込んだり)もありますが、基本的にはパッケージ変数の初期化に使われます。

パッケージ変数は、代入文でも初期化を行うことがありますが、以下のような場合にinit関数を使います。


  • 代入文で表現できない初期化(forを使うなど)


    • もちろん、init関数でやらず、別の関数を呼び出してそれを代入してもよい



  • 初期化処理が複雑

  • 他のパッケージのパッケージ変数を初期化してやる



    • image/pngimage/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で動かす

言語仕様を読む限り、依存するパッケージがある場合(import文がある場合)には、それらのパッケージの初期化が終わった後に自身のパッケージのinit関数が実行されるようです。


init関数はいくつ書けるのか?

よくよく考えると、複数ファイルにまたがって、1つのパッケージに複数のinit関数を書いていることがあるのに気づくことがあります。他の関数の場合は、複数定義すると、もちろんコンパイルエラーになります。

ちなみに、ファイルに1つという決まりもなく、複数書けるようになっています。

package main

import (
"fmt"
)

func init() {
fmt.Print("hello")
}

func init() {
fmt.Println(", init")
}

func main() {
}

Playgroundで動かす

不思議ですね。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で動かす

実行すると、以下のように出力されます。

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で動かす

実行すると、以下のようにエラーがでます。

tmp/sandbox671239315/main.go:10: undefined: init

定義されていないことになるようです。なるほど!


おわりに

init関数についてまとめてみました。

ちなみに、この記事を書こうと思った元ネタはこちらです。これも面白いので、ぜひ読んでみて下さい!