Package initialization
Go プログラムを実行するとき、Go コンパイラーはパッケージ、パッケージ内のファイル、それらのファイル内の変数宣言について一定の実行順序に従います。
Package scope
A scope とはコードブロック内の領域で定義済み変数にアクセスできる場所。 パッケージスコープは、宣言された変数がパッケージ内から(パッケージ内のすべてのファイルにわたって)アクセス可能なパッケージ内の領域である。 この領域は、パッケージ内のどのファイルでも最上位のブロックです。
![](https://miro.medium.com/max/60/1*wgXZOSz73eYtzxi5Ysd4mQ.png?q=20)
![](https://miro.medium.com/max/60/1*5z2ws2w_bJiGOLrbwIOpyQ.png?q=20)
go run
コマンドを見てみましょう。 今回は1つのファイルを実行するのではなく、app
パッケージ内のすべてのファイルをインクルードして実行しています。
Go は賢いので、main
関数を持っているので、アプリケーションのエントリポイントは entry.go
であると判断しています。
go run src/app/version.go src/app/entry.go
💡 同じことをする
go run app
コマンドも使用でき、パッケージを実行するにはこちらの方が良い方法です。go install
またはgo build
コマンドはパッケージ名を必要とし、パッケージ内のすべてのファイルを含むので、上記のように指定する必要はありません。
本題に戻りますが、version.go
ファイルで宣言された変数 version
はパッケージのどの場所からでも使用できますが、それが (Version
と同様に) エクスポートされていないとしても、それはパッケージスコープ内で宣言されているので、使用できます。
変数 version
が関数内で宣言されていた場合、パッケージスコープに存在しないため、上記のプログラムはコンパイルに失敗していたでしょう。 したがって、一度version
変数を宣言すると、パッケージスコープで再宣言することはできません。
![](https://miro.medium.com/max/60/1*MkeLqeyTgm-lgDKkSUG-_w.png?q=20)
Variable initialization
ある変数 a
が別の変数 b
に依存するとき、あらかじめ b
を定義しなければプログラムはコンパイルされない。
![](https://miro.medium.com/max/60/1*RY-43HY_xNSL0XKb__X7_Q.png?q=20)
しかしこれらの変数はパッケージスコープで定義すると、初期化サイクルで宣言されることになります。
![](https://miro.medium.com/max/60/1*XG6GzH8LDDAlEMMRraDoTg.png?q=20)
上記の例では、まずc
が宣言されていますが、これはその値がすでに宣言されているためです。 その後の初期化サイクルでは、b
が宣言されるが、これはc
に依存しており、c
の値がすでに宣言されているためである。 最後の初期化サイクルでは、a
が宣言され、b
の値に代入される。 2919>
![](https://miro.medium.com/max/60/1*J2mPK9rNQl3td4ehQGw3mg.png?q=20)
上記の例では、まず c
が宣言されて、次に c
に値が依存しているので b
、最後に値が b
に依存しているので a
が宣言されることになります。
![](https://miro.medium.com/max/60/1*o37taeuBhrY5J3dNuSm-sg.png?q=20)
パッケージスコープの別の例としては、別のファイルにある関数 f
がメインファイルから変数 c
にアクセスしているような場合です。
![](https://miro.medium.com/max/60/1*L0v9SiAKWopNELfyNIHUAQ.png?q=20)
![](https://miro.medium.com/max/60/1*_Vk2ggc4NYJ5wnqjuxqVlw.png?q=20)
Init function
main
関数と同様です。 init
関数はパッケージが初期化されるときに呼び出されます。 引数を取らず、値も返しません。
init
関数は Go によって暗黙的に宣言されているので、どこからでも参照することはできません (または init()
のように呼び出すことはできません)。 ファイルやパッケージの中に複数の init
関数を持つことができます。
![](https://miro.medium.com/max/60/1*7opoqANNzsqLyHViQaK-tg.png?q=20)
パッケージのどこにでも init
関数を置くことができます。 これらのinit
関数は辞書的ファイル名順(アルファベット順)に呼ばれます。
![](https://miro.medium.com/max/60/1*v_M5sIeoZ8W49oMs96Z7qQ.png?q=20)
全てのinit
関数が実行されてから、main
関数が呼ばれることになります。 したがって、init
関数の主な仕事は、グローバルコンテキストで初期化できないグローバル変数を初期化することである。 例えば、配列の初期化。
![](https://miro.medium.com/max/60/1*UswvbXJpnMDT-kkeAqnzZw.png?q=20)
パッケージスコープではfor
構文は有効ではないので、サイズ10
の配列 integers
をinit
関数内のfor
ループを使って初期化することが可能である。
パッケージエイリアス
パッケージをインポートすると、そのパッケージのパッケージ宣言を使用して変数が作成されます。 もし、同じ名前の複数のパッケージをインポートしている場合、これは衝突の原因になります。
// greet/parent.go
package greetvar Message = "Hey there. I am parent."// greet/greet/child.go
package greetvar Message = "Hey there. I am child."
![](https://miro.medium.com/max/60/1*jcIZqvdcS5kdHNtlsYmfSQ.png?q=20)
したがって、パッケージ・エイリアスを使用しています。
![](https://miro.medium.com/max/60/1*xc5a7RQCtxbvZ-M8UZCrWw.png?q=20)
上の例では、greet/greet
パッケージは現在 child
変数に参照されています。
アンダースコアは Go の特殊文字で、null
コンテナとして機能します。 greet
パッケージをインポートしているが使用していないので、Go コンパイラーは文句を言うだろう。 それを避けるために、そのパッケージの参照を _
に保存し、Go コンパイラーは単にそれを無視します。
何もしないように見えるアンダースコアでパッケージを別名にすることは、パッケージを初期化したいが使用しない場合に非常に便利です。
// greet/parent.go
package greetimport "fmt"var Message = "Hey there. I am parent."func init() {
fmt.Println("greet/parent.go ==> init()")
}// greet/greet/child.go
package greetimport "fmt"var Message = "Hey there. I am child."func init() {
fmt.Println("greet/greet/child.go ==> init()")
}
![](https://miro.medium.com/max/60/1*RhDn0T7hbV_Fy8VmG2fUZw.png?q=20)
覚えておくべきことは、imported パッケージはパッケージごとに一度だけ初期化されることです。 したがって、パッケージ内に複数の import ステートメントがある場合、インポートされたパッケージはメイン パッケージの実行期間中に一度だけ初期化されます。