Everything you need to know about Packages in Go

Package initialization

Go プログラムを実行するとき、Go コンパイラーはパッケージ、パッケージ内のファイル、それらのファイル内の変数宣言について一定の実行順序に従います。

Package scope

A scope とはコードブロック内の領域で定義済み変数にアクセスできる場所。 パッケージスコープは、宣言された変数がパッケージ内から(パッケージ内のすべてのファイルにわたって)アクセス可能なパッケージ内の領域である。 この領域は、パッケージ内のどのファイルでも最上位のブロックです。

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変数を宣言すると、パッケージスコープで再宣言することはできません。

Variable initialization

ある変数 a が別の変数 b に依存するとき、あらかじめ b を定義しなければプログラムはコンパイルされない。

しかしこれらの変数はパッケージスコープで定義すると、初期化サイクルで宣言されることになります。

上記の例では、まずcが宣言されていますが、これはその値がすでに宣言されているためです。 その後の初期化サイクルでは、bが宣言されるが、これはcに依存しており、cの値がすでに宣言されているためである。 最後の初期化サイクルでは、a が宣言され、b の値に代入される。 2919>

上記の例では、まず c が宣言されて、次に c に値が依存しているので b 、最後に値が b に依存しているので a が宣言されることになります。

パッケージスコープの別の例としては、別のファイルにある関数 f がメインファイルから変数 c にアクセスしているような場合です。

Init function

main関数と同様です。 init関数はパッケージが初期化されるときに呼び出されます。 引数を取らず、値も返しません。

init 関数は Go によって暗黙的に宣言されているので、どこからでも参照することはできません (または init() のように呼び出すことはできません)。 ファイルやパッケージの中に複数の init 関数を持つことができます。

パッケージのどこにでも init 関数を置くことができます。 これらのinit関数は辞書的ファイル名順(アルファベット順)に呼ばれます。

全てのinit関数が実行されてから、main関数が呼ばれることになります。 したがって、init関数の主な仕事は、グローバルコンテキストで初期化できないグローバル変数を初期化することである。 例えば、配列の初期化。

パッケージスコープではfor構文は有効ではないので、サイズ10の配列 integersinit関数内のforループを使って初期化することが可能である。

パッケージエイリアス

パッケージをインポートすると、そのパッケージのパッケージ宣言を使用して変数が作成されます。 もし、同じ名前の複数のパッケージをインポートしている場合、これは衝突の原因になります。

// greet/parent.go
package greetvar Message = "Hey there. I am parent."// greet/greet/child.go
package greetvar Message = "Hey there. I am child."

したがって、パッケージ・エイリアスを使用しています。

上の例では、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()")
}

覚えておくべきことは、imported パッケージはパッケージごとに一度だけ初期化されることです。 したがって、パッケージ内に複数の import ステートメントがある場合、インポートされたパッケージはメイン パッケージの実行期間中に一度だけ初期化されます。