プログラムの初期化と実行
ゼロ値
変数のための記憶域が宣言やnewの呼び出しによって割り当てられる場合、または新しい値が複合リテラルやmakeの呼び出しによって作成される場合、明示的な初期化が提供されていなければ、変数または値はデフォルト値が与えられます。このような変数または値の各要素はその型のゼロ値に設定されます:ブール型ではfalse
、数値型では0
、文字列では""
、そしてポインタ、関数、インターフェース、スライス、チャネル、およびマップではnil
です。この初期化は再帰的に行われるため、例えば値が指定されていない場合、構造体の配列の各要素はそのフィールドがゼロになります。
以下の2つの単純な宣言は同等です:
var i int
var i int = 0
次の場合:
type T struct { i int; f float64; next *T }
t := new(T)
以下が成り立ちます:
t.i == 0
t.f == 0.0
t.next == nil
以下の場合も同様です:
var t T
パッケージの初期化
パッケージ内で、パッケージレベルの変数初期化は段階的に進行し、各ステップでは宣言順で最も早く、初期化されていない変数に依存関係のない変数が選択されます。
より正確には、パッケージレベルの変数は、まだ初期化されておらず、初期化式がないか、初期化式が初期化されていない変数に依存関係を持たない場合、初期化の準備ができていると見なされます。初期化は、宣言順で最も早く初期化の準備ができているパッケージレベルの次の変数を、初期化の準備ができている変数がなくなるまで繰り返し初期化することによって進行します。
このプロセスが終了したときにまだ初期化されていない変数がある場合、それらの変数は1つ以上の初期化サイクルの一部であり、プログラムは有効ではありません。
右側の単一の(複数の値を持つ)式によって初期化される変数宣言の左側にある複数の変数は、一緒に初期化されます:左側の変数のいずれかが初期化される場合、それらの変数はすべて同じステップで初期化されます。
var x = a
var a, b = f() // aとbは一緒に初期化され、xが初期化される前に初期化される
パッケージの初期化の目的では、ブランク識別子は宣言内の他の変数と同様に扱われます。
複数のファイルで宣言された変数の宣言順序は、ファイルがコンパイラに提示される順序によって決まります:最初のファイルで宣言された変数は、2番目のファイルで宣言された変数の前に宣言され、以下同様です。再現可能な初期化動作を確保するために、ビルドシステムは、同じパッケージに属する複数のファイルを辞書順のファイル名順でコンパイラに提示することが推奨されます。
依存関係分析は、変数の実際の値に依存せず、ソース内のそれらへの語彙的な参照のみに依存し、推移的に分析されます。例えば、変数x
の初期化式が変数y
を参照する関数の本体を参照している場合、x
はy
に依存します。具体的には:
- 変数または関数への参照は、その変数または関数を示す識別子です。
- メソッド
m
への参照は、t.m
形式のメソッド値またはメソッド式です。ここでt
の(静的)型はインターフェース型ではなく、メソッドm
はt
のメソッドセット内にあります。結果の関数値t.m
が呼び出されるかどうかは重要ではありません。 - 変数、関数、またはメソッド
x
は、x
の初期化式または本体(関数とメソッドの場合)にy
への参照、またはy
に依存する関数やメソッドへの参照が含まれている場合、変数y
に依存します。
例えば、次の宣言が与えられた場合:
var (
a = c + b // == 9
b = f() // == 4
c = f() // == 5
d = 3 // == 初期化が完了した後は5
)
func f() int {
d++
return d
}
初期化順序はd
、b
、c
、a
です。初期化式の部分式の順序は関係ないことに注意してください:この例ではa = c + b
とa = b + c
は同じ初期化順序になります。
依存関係分析はパッケージごとに実行されます。現在のパッケージで宣言された変数、関数、および(非インターフェース)メソッドを参照する参照のみが考慮されます。他の隠れたデータ依存関係が変数間に存在する場合、それらの変数間の初期化順序は指定されていません。
例えば、次の宣言が与えられた場合:
var x = I(T{}).ab() // xはaとbに未検出の隠れた依存関係がある
var _ = sideEffect() // x、a、またはbとは無関係
var a = b
var b = 42
type I interface { ab() []int }
type T struct{}
func (T) ab() []int { return []int{a, b} }
変数a
はb
の後に初期化されますが、x
がb
の前に初期化されるか、b
とa
の間に初期化されるか、またはa
の後に初期化されるか、そして同様にsideEffect()
が呼び出される瞬間(x
が初期化される前か後か)は指定されていません。
変数は、パッケージブロックで宣言された引数と結果パラメータを持たないinit
という名前の関数を使用して初期化することもできます。
func init() { … }
そのような関数は、単一のソースファイル内でも、パッケージごとに複数定義できます。パッケージブロックでは、init
識別子はinit
関数を宣言するためにのみ使用できますが、識別子自体は宣言されていません。したがって、init
関数はプログラムのどこからも参照できません。
パッケージ全体は、すべてのパッケージレベル変数に初期値を割り当て、その後、ソース内に現れる順序ですべてのinit
関数を呼び出すことによって初期化されます。これらの関数は、コンパイラに提示されるように、おそらく複数のファイルに存在します。
プログラムの初期化
完全なプログラムのパッケージは、一度に1つのパッケージずつ段階的に初期化されます。パッケージにインポートがある場合、インポートされたパッケージは、パッケージ自体を初期化する前に初期化されます。複数のパッケージが同じパッケージをインポートする場合、インポートされたパッケージは一度だけ初期化されます。パッケージのインポートは、構造上、循環的な初期化依存関係がないことを保証します。より正確には:
すべてのパッケージのリストが与えられ、インポートパスでソートされている場合、各ステップで、リスト内の初期化されていない最初のパッケージで、インポートされたすべてのパッケージ(もしあれば)がすでに初期化されているものが初期化されます。このステップは、すべてのパッケージが初期化されるまで繰り返されます。
パッケージの初期化—変数の初期化とinit
関数の呼び出し—は、単一のゴルーチンで、順次、一度に1つのパッケージずつ行われます。init
関数は他のゴルーチンを起動することがあり、それらは初期化コードと並行して実行できます。ただし、初期化は常にinit
関数を順序付けします:前の関数が戻るまで次の関数を呼び出しません。
プログラムの実行
完全なプログラムは、メインパッケージと呼ばれる単一の、インポートされていないパッケージを、それがインポートするすべてのパッケージと推移的にリンクすることによって作成されます。メインパッケージは、パッケージ名がmain
であり、引数を取らず値を返さないmain
関数を宣言する必要があります。
func main() { … }
プログラムの実行は、プログラムを初期化し、その後パッケージmain
内の関数main
を呼び出すことから始まります。その関数呼び出しが戻ると、プログラムは終了します。他の(main
以外の)ゴルーチンが完了するのを待ちません。