golangのinit関数は複数回定義できる
はじめに
そのまんまタイトル通りの内容ですw
Github で いくつかgolangのリポジトリをごそごそしている時に、init関数
が1ファイルに対して、複数回定義されているのを発見しました。
Packageの初期化をするための関数という認識だったので、勝手に『1回だけの定義なんだろうな。』と思っていました。お恥ずかしい。
復習も含めて、再度init
関数について調べなおしてみました。
init関数の役割
init関数は特殊な関数で、Packageの初期化を行う際に機能します。
package main import ( "fmt" ) var message string func init() { message = "Hello world" } func main() { fmt.Println(message) }
main
関数実行前の初期化として作用します。
init関数の呼び出し
init
関数は、PackageをImportした際に呼び出されます。
あくまで、『importした際』に呼び出されるのがポイントです。
これを検証するために、下記のような構造のソースコードを用意しました。
src --- main.go | | --- lib1 | | --- lib.go | | --- lib2 | --- lib.go
// /src/lib1/lib.go package lib1 import "fmt" var libName = "lib1" func init() { fmt.Printf("%s init called!!\r\n", libName) } func Func() { fmt.Printf("%s Func Done.\r\n", libName) }
// /src/lib2/lib.go package lib2 import "fmt" var libName = "lib2" func init() { fmt.Printf("%s init called!!\r\n", libName) } func Func() { fmt.Printf("%s Func Done.\r\n", libName) }
Packageに相当する2つのファイルでは、それぞれinit
関数を定義しており、呼び出しのタイミングでConsole上に文字を出力しています。
/src/main.go package main import ( "fmt" "github.com/atEaE/go-sample/lib2" "github.com/atEaE/go-sample/lib1" ) func init() { fmt.Println("main called") } func main() { lib1.Func() lib2.Func() fmt.Println("Hello world!") }
main.go
では、2つのPackageを参照し、それぞれmain
関数の中でPackageの関数を呼び出しています。
ここでは呼び出し順を確認するために、下記ような順番でソースコードを記載しています。
lib2
をImportする.lib1
をImportする.lib1
内のFunc
関数を呼び出す。lib2
内のFunc
関数を呼び出す。
※ VSCode でgofmt
などをしていると保存の際に自動的に並び順がフォーマットされてしまうので、あえてメモ帳などを使って編集しています。
なんとなく(私だけかも知れないが...)、main
関数内の記載順に呼びされそうな感じがしますが、上のコードを実行するとimport順にinit
関数が呼びされることが確認できます。
C:\workspace\sample\go-sample> go run main.go lib2 init called!! lib1 init called!! main called lib1 Func Done. lib2 Func Done. Hello world!
加えて、呼び出し元であるmain.go
のinit
関数はimport終了後に呼びされているのが確認できます。
init関数の定義
init
関数は1ファイルに限らず、Package内で複数回定義ができます。
これについては、公式のドキュメント内の『Package initialization』の項に記載がありました。
Multiple such functions may be defined per package, even within a single source file.
--- このような関数(init関数)は、1つのソースファイル内であっても、パッケージごとに複数定義することができます。--- The Go Programming Language Specification
これも検証用のソースコードを用意しました。
src --- main.go | | --- lib | | --- lib1.go | --- lib2.go
// /src/lib/lib1.go package lib import "fmt" var multiPle1 string func init() { multiPle1 = "lib1 init called" } func init() { fmt.Printf("%s\r\n", multiPle1) } func Hello() { fmt.Print("Hello, ") }
// /src/lib/lib2.go package lib import "fmt" var multiPle2 string func init() { multiPle2 = "lib2 init called" } func init() { fmt.Printf("%s\r\n", multiPle2) } func World() { fmt.Print("World!!") }
lib
Package全体でみると4回のinit
関数が定義されており、各ファイルで2回ずつinit
関数を定義しています。
// /src/main.go package main import "github.com/atEaE/go-sample/lib" func main() { lib.Hello() lib.World() }
このコードの事項結果は下記のようになります。
C:\workspace\sample\go-sample> go run main.go lib1 init called lib2 init called Hello, World!!
初期化処理が煩雑な場合などは、init
を分割した方がメンテナンスが高くなりそうですね。
まとめ
init
関数はimport時に呼び出される。init
関数は1ファイルに限らず, Package毎に複数回定義できる。